<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>Coder Legion - Recent posts tagged payment-integration</title>
<link>https://coderlegion.com/tag/payment-integration</link>
<description>Powered by Question2Answer</description>
<item>
<title>Why Most Crypto Payment Startups Get Stuck at Compliance (and How to Launch Without a 12-Month Delay</title>
<link>https://coderlegion.com/31303/why-most-crypto-payment-startups-get-stuck-compliance-and-how-launch-without-month-delay</link>
<description>&lt;p&gt;Launching a crypto payment startup is not just a technology challenge. Founders must figure out how payments will move, which jurisdictions they can serve, how customer funds will be handled, and what regulatory obligations apply to their business model.&lt;/p&gt;
&lt;p&gt;Many teams start by building a payment dashboard, integrating blockchain networks, and connecting merchants. Then they discover that licensing questions, banking relationships, transaction monitoring, and compliance requirements can affect the entire launch plan.&lt;/p&gt;
&lt;p&gt;The result is often months of rework, delayed partnerships, and development costs that continue to grow before the first transaction is processed.&lt;/p&gt;
&lt;p&gt;The solution is not to bypass compliance or rush into production. It is to identify regulatory requirements early, choose the right payment architecture, and build compliance considerations into the development process from the beginning.&lt;/p&gt;
&lt;p&gt;For founders planning a crypto payment business, this approach can help reduce avoidable delays while preparing the platform for a more controlled launch.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Why Crypto Payment Startups Get Stuck at Compliance?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Compliance problems rarely appear in isolation. They often reveal decisions that should have been addressed before development began.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. Choosing a Business Model Before Understanding Regulatory Requirements&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;A crypto payment gateway that allows merchants to accept digital assets may have different obligations from a platform that converts cryptocurrency into fiat currency, holds customer funds, or transfers assets between users.&lt;/p&gt;
&lt;p&gt;These distinctions matter because regulatory treatment depends on the activities performed, the jurisdictions involved, and the entities handling the transactions.&lt;/p&gt;
&lt;p&gt;For example, a startup that only provides payment software may have a different compliance profile from one that receives, converts, or transmits customer funds.&lt;/p&gt;
&lt;p&gt;Founders should map the complete payment flow before choosing a technical architecture. This includes identifying who receives the payment, who controls the assets, whether conversion occurs, and which third parties participate.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. Treating KYC and AML as Features to Add Later&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Know Your Customer (KYC) and Anti-Money Laundering (AML) controls should not be treated as optional dashboard features that can simply be added before launch.&lt;/p&gt;
&lt;p&gt;Depending on the business model and applicable law, a payment platform may need customer identification, risk classification, sanctions screening, transaction monitoring, recordkeeping, and suspicious activity reporting processes.&lt;/p&gt;
&lt;p&gt;The Financial Action Task Force (FATF) sets international standards for virtual asset service providers, including relevant customer due diligence and transaction monitoring obligations. Local implementation and requirements vary by jurisdiction.&lt;/p&gt;
&lt;p&gt;When founders postpone these decisions, they may need to redesign onboarding, transaction processing, data storage, and administrative workflows later.&lt;/p&gt;
&lt;p&gt;A better approach is to define the applicable compliance requirements before development and translate them into technical and operational requirements.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. Underestimating Licensing and Jurisdictional Requirements&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;A crypto payment startup cannot assume that operating online removes the need for regulatory authorization.&lt;/p&gt;
&lt;p&gt;Some jurisdictions require specific licenses or registrations for certain payment, money transmission, or virtual asset activities. Others restrict particular services or impose additional requirements on businesses serving local customers.&lt;/p&gt;
&lt;p&gt;For example, in the United States, FinCEN guidance explains that certain virtual currency administrators and exchangers may qualify as money transmitters, depending on their activities and applicable exceptions. State-level requirements may also be relevant.&lt;/p&gt;
&lt;p&gt;Founders must assess their actual activities rather than relying on a generic checklist.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Before development begins, determine:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Where the company will be incorporated and operate&lt;/li&gt;
&lt;li&gt;Which countries or regions the platform will serve&lt;/li&gt;
&lt;li&gt;Whether the business will custody, exchange, or transmit digital&lt;br&gt;
assets&lt;/li&gt;
&lt;li&gt;Whether fiat currency conversion or payment processing is involved&lt;/li&gt;
&lt;li&gt;Which licenses, registrations, or regulatory approvals may apply&lt;/li&gt;
&lt;li&gt;Which banking, payment, and compliance partners are required&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Getting these questions answered early helps prevent a platform from being built around an operating model that cannot be used as planned.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;4. Waiting Too Long to Approach Banking and Payment Partners&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;A payment platform may be technically functional but still struggle to launch because its banking, fiat payment, or conversion partners have not approved the proposed business model.&lt;/p&gt;
&lt;p&gt;These providers often conduct their own due diligence. They may request information about company ownership, transaction flows, customer profiles, source-of-funds controls, expected volumes, and compliance procedures.&lt;/p&gt;
&lt;p&gt;If founders approach potential partners only after development is complete, an approval delay can leave the entire project waiting.&lt;/p&gt;
&lt;p&gt;Instead, engage relevant providers during the planning phase. Confirm their supported jurisdictions, onboarding criteria, settlement methods, integration requirements, and restrictions before committing to the final architecture.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;5. Building a Payment System That Cannot Support Compliance&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;A crypto payment gateway needs more than the ability to generate wallet addresses and detect blockchain transactions.&lt;/p&gt;
&lt;p&gt;Depending on its operating model, the platform may also require transaction records, payment status tracking, customer verification workflows, risk alerts, administrative review tools, and reporting capabilities.&lt;/p&gt;
&lt;p&gt;If these requirements are overlooked, developers may need to rebuild major parts of the system to support them.&lt;/p&gt;
&lt;p&gt;A well-planned architecture should account for compliance-related data and workflows from the start, while applying appropriate access controls, data protection, and retention policies.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How to Reduce Compliance Delays Without Cutting Corners&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;There is no universal way to launch every crypto payment startup within a fixed number of weeks. &lt;/p&gt;
&lt;p&gt;However, founders can reduce avoidable delays by handling regulatory planning and technical development in parallel where appropriate.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Step 1: Define the Exact Payment Model&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Start by documenting how the platform will operate.&lt;/p&gt;
&lt;p&gt;Will merchants receive crypto directly, or will the platform convert payments into fiat currency? Will the company hold customer assets? Will it provide custody, exchange services, or only payment software?&lt;/p&gt;
&lt;p&gt;These decisions influence licensing analysis, custody arrangements, third-party integrations, and security requirements.&lt;/p&gt;
&lt;p&gt;A clearly defined model also helps developers avoid building unnecessary features or choosing infrastructure that does not fit the business.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Step 2: Conduct a Jurisdiction-Specific Compliance Assessment&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Work with qualified legal and compliance professionals to identify the requirements that apply to the proposed activities and target markets.&lt;/p&gt;
&lt;p&gt;This assessment should cover relevant licensing or registration obligations, KYC and AML controls, sanctions screening, customer disclosures, data protection, transaction monitoring, and reporting requirements.&lt;/p&gt;
&lt;p&gt;Create a compliance checklist that identifies the responsible party, required evidence, dependencies, and expected completion milestones.&lt;/p&gt;
&lt;p&gt;Do not assume that a vendor's compliance tools automatically make the entire business compliant. The company remains responsible for understanding its own legal obligations.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Step 3: Choose Suitable Technology and Integration Partners&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The development team should understand the business model before selecting blockchain networks, wallet infrastructure, conversion services, and payment integrations.&lt;/p&gt;
&lt;p&gt;Evaluate providers based on supported jurisdictions, technical compatibility, security practices, service availability, onboarding requirements, and total operating costs.&lt;/p&gt;
&lt;p&gt;For the software itself, prioritize a modular architecture that can support necessary payment workflows and compliance integrations without requiring a complete rebuild whenever requirements change.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Step 4: Build Compliance Workflows Into the Platform&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Once the applicable requirements are understood, translate them into system functionality.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Depending on the business model, this could include:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Customer onboarding and identity verification integrations&lt;/li&gt;
&lt;li&gt;Risk-based transaction monitoring&lt;/li&gt;
&lt;li&gt;Sanctions screening and alert handling&lt;/li&gt;
&lt;li&gt;Secure audit logs and transaction records&lt;/li&gt;
&lt;li&gt;Payment status tracking and reconciliation&lt;/li&gt;
&lt;li&gt;Administrative review and escalation workflows&lt;/li&gt;
&lt;li&gt;Role-based access controls and reporting tools&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Not every platform needs every feature. The goal is to build the controls required for the specific business rather than adding a long list of generic features.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Step 5: Develop and Validate the Payment Infrastructure&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;With the business model and core requirements established, the team can develop the payment workflows.&lt;/p&gt;
&lt;p&gt;Essential components may include payment request generation, blockchain transaction monitoring, confirmation handling, merchant dashboards, payment notifications, settlement logic, and reconciliation.&lt;/p&gt;
&lt;p&gt;Testing should cover failed transactions, delayed confirmations, duplicate payment notifications, incorrect payment amounts, network congestion, integration outages, and other relevant failure scenarios.&lt;/p&gt;
&lt;p&gt;Security testing should also examine access permissions, sensitive data handling, API protection, and the security of wallet or custody integrations.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Step 6: Complete Partner Reviews and Pre-Launch Checks&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Before accepting live transactions, confirm that the required approvals, partner agreements, technical integrations, and operational procedures are in place.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The pre-launch checklist should include:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Applicable licenses or registrations and required approvals&lt;/li&gt;
&lt;li&gt;Completion of required provider onboarding&lt;/li&gt;
&lt;li&gt;Verified payment and settlement workflows&lt;/li&gt;
&lt;li&gt;Tested compliance controls and escalation procedures&lt;/li&gt;
&lt;li&gt;Security assessments and remediation of critical findings&lt;/li&gt;
&lt;li&gt;Reconciliation and incident-response procedures&lt;/li&gt;
&lt;li&gt;Monitoring, customer support, and operational ownership&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A platform should go live only when the relevant legal, operational, and technical conditions have been satisfied.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Can a Crypto Payment Gateway Launch Without a 12-Month Delay?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Yes, some projects can reach launch sooner than 12 months, but the timeline depends on what is being built and which approvals are required.&lt;/p&gt;
&lt;p&gt;A startup using established payment infrastructure and supported third-party services may have a shorter development path than a company building its own custody systems, conversion infrastructure, and extensive payment integrations.&lt;/p&gt;
&lt;p&gt;The distinction between software readiness and regulatory readiness is particularly important.&lt;/p&gt;
&lt;p&gt;Some activities can proceed in parallel, such as developing non-regulated software components while the company works through its legal assessment. Other activities depend on prior decisions or approvals.&lt;/p&gt;
&lt;p&gt;A development company can help organize the technical work and reduce unnecessary rework, but it cannot guarantee regulatory approval or eliminate delays controlled by authorities, banks, or third-party providers.&lt;/p&gt;
&lt;p&gt;The most useful planning approach is to establish separate milestones for technical development, partner onboarding, compliance readiness, testing, and production authorization.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What Should Founders Look for in a Crypto Payment Gateway Development Company?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Choosing a development partner involves more than comparing feature lists or hourly rates. The team should understand payment workflows and be able to build infrastructure around the startup's actual business requirements.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Experience With Payment Architecture&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Look for experience with blockchain integrations, payment processing, transaction monitoring, merchant dashboards, payment notifications, and settlement workflows.&lt;/p&gt;
&lt;p&gt;Ask how the proposed architecture handles delayed transactions, failed payments, reconciliation, and service interruptions.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Integration Flexibility&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The platform may need to connect with identity verification vendors, blockchain analytics providers, fiat payment processors, conversion services, or custody providers.&lt;/p&gt;
&lt;p&gt;Confirm which integrations are available, which require additional development, and whether each provider supports the intended markets and business model.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Security and Operational Controls&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Evaluate authentication, access management, sensitive data protection, wallet integration security, logging, incident response, and testing practices.&lt;/p&gt;
&lt;p&gt;Request clear documentation of what is included in the development scope and what needs to be supplied by external providers.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Transparent Scope and Delivery Milestones&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;A reliable development proposal should identify deliverables, dependencies, integration costs, testing requirements, and the responsibilities of both parties.&lt;/p&gt;
&lt;p&gt;It should also distinguish software delivery from legal advice, licensing, partner approval, and other activities outside the development team's control.&lt;/p&gt;
&lt;p&gt;For businesses evaluating their options, a &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.dappfort.com/crypto-payment-gateway-development/&quot;&gt;crypto payment gateway development company&lt;/a&gt; can be a useful starting point for discussing payment architecture, blockchain integration, merchant workflows, and the technical requirements of a proposed platform.&lt;/p&gt;
&lt;p&gt;Dappfort provides crypto payment gateway development services for businesses planning payment infrastructure around their requirements. Before starting a project, discuss the required payment flows, supported assets, third-party integrations, security expectations, delivery milestones, and the division of responsibilities for compliance.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Common Mistakes That Can Extend the Launch Timeline&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Even a well-funded startup can lose months by making avoidable planning decisions.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Building before defining the operating model:&lt;/strong&gt; Technical decisions made before understanding the business and regulatory requirements may need to be reversed.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Assuming one license covers every market:&lt;/strong&gt; Requirements can differ across jurisdictions and business activities.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Treating KYC and AML as vendor-only responsibilities:&lt;/strong&gt; Third-party tools may support compliance, but they do not automatically fulfill every obligation of the business.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Leaving partner onboarding until the end:&lt;/strong&gt; Banking, conversion, and payment provider reviews can become critical-path dependencies.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Launching before testing the full payment lifecycle:&lt;/strong&gt; A successful payment request does not prove that confirmations, settlement, reconciliation, refunds, and exception handling work correctly.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Confusing a completed product with launch readiness:&lt;/strong&gt; Software can be ready before the business has completed the approvals and operational preparations needed to use it.&lt;/p&gt;
&lt;p&gt;Avoiding these mistakes does not remove every source of delay. It gives founders better control over the dependencies they can identify and manage.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Final Thoughts&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Compliance is not simply a final checkpoint for a crypto payment startup. It influences the business model, payment architecture, partner selection, customer onboarding, and the conditions under which the platform can operate.&lt;/p&gt;
&lt;p&gt;Founders who address these questions early can make better technology decisions, avoid preventable redevelopment, and create a more realistic launch plan.&lt;/p&gt;
&lt;p&gt;The goal should not be to bypass a 12-month process at any cost. It should be to distinguish necessary regulatory and operational work from avoidable delays caused by poor planning.&lt;/p&gt;
&lt;p&gt;With a clearly defined payment model, appropriate legal guidance, suitable integration partners, and a development team that understands the payment lifecycle, startups can work toward a faster and more controlled launch.&lt;/p&gt;
&lt;p&gt;Before investing in a full build, document the required payment flows, confirm the relevant compliance obligations, and agree on realistic milestones for development, testing, approvals, and production readiness.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/31303/why-most-crypto-payment-startups-get-stuck-compliance-and-how-launch-without-month-delay</guid>
<pubDate>Fri, 16 Oct 2026 07:58:28 +0000</pubDate>
</item>
<item>
<title>How to Use Invoicing and Payment Gateway Tools for Freelance Web Developers</title>
<link>https://coderlegion.com/27996/how-to-use-invoicing-and-payment-gateway-tools-for-freelance-web-developers</link>
<description>&lt;p&gt;Freelance web development is not only about writing code, building websites, fixing bugs, or maintaining client projects. The business side matters just as much. A developer can complete a project successfully and still create cash-flow problems by using poor invoicing practices, offering inconvenient payment methods, or failing to track outstanding balances.&lt;/p&gt;
&lt;p&gt;Invoicing and payment gateway tools solve much of this administrative work. They allow freelance developers to create professional invoices, collect deposits, accept online payments, track payment status, and maintain records without manually managing every transaction.&lt;/p&gt;
&lt;p&gt;The right setup also makes it easier to work with clients in different countries. Tools such as Wise Business, PayPal, and FreshBooks offer different combinations of invoicing, payment links, online payment processing, and financial administration. Their availability, supported currencies, payment methods, fees, and eligibility vary by country and account type, so developers should verify the applicable terms before choosing a service.&lt;/p&gt;
&lt;p&gt;Why Freelance Web Developers Need a Proper Payment System&lt;br&gt;
A freelance developer may handle several types of billing:&lt;/p&gt;
&lt;p&gt;Fixed-price website projects&lt;br&gt;
Hourly development work&lt;br&gt;
Website maintenance&lt;br&gt;
Hosting and technical support&lt;br&gt;
Monthly retainers&lt;br&gt;
Plugin or theme customization&lt;br&gt;
Emergency bug fixes&lt;br&gt;
Consulting&lt;br&gt;
SEO and technical optimization&lt;br&gt;
Recurring development services&lt;br&gt;
Trying to manage all of these through informal messages and bank-transfer screenshots quickly becomes difficult.&lt;/p&gt;
&lt;p&gt;A professional invoice establishes exactly what the client is paying for, how much is due, the payment deadline, and how the client can pay. It also creates a financial record that can be reconciled later.&lt;/p&gt;
&lt;p&gt;For international clients, payment gateways and payment platforms can remove some of the friction associated with cross-border payments.&lt;/p&gt;
&lt;p&gt;What an Effective Developer Invoice Should Contain&lt;br&gt;
A professional invoice does not need to be complicated. It needs to be complete.&lt;/p&gt;
&lt;p&gt;At minimum, include:&lt;/p&gt;
&lt;p&gt;Your business or professional name.&lt;br&gt;
Your contact information.&lt;br&gt;
The client's name and billing information.&lt;br&gt;
A unique invoice number.&lt;br&gt;
The invoice date.&lt;br&gt;
A clear description of the services.&lt;br&gt;
The quantity or hours where applicable.&lt;br&gt;
The price for each service.&lt;br&gt;
Applicable taxes or other charges.&lt;br&gt;
The total amount due.&lt;br&gt;
The currency.&lt;br&gt;
The payment deadline.&lt;br&gt;
Available payment methods.&lt;br&gt;
Relevant payment terms.&lt;br&gt;
For a website project, avoid writing only &quot;Website development&quot; as the description. A more useful invoice could separate the work into discovery, design implementation, frontend development, backend development, deployment, and maintenance.&lt;/p&gt;
&lt;p&gt;This gives the client a clear record of what was purchased.&lt;/p&gt;
&lt;p&gt;Choose Invoicing Software That Matches Your Workflow&lt;br&gt;
There is no single invoicing platform that every freelance developer should use. The important issue is whether the tool fits the developer's clients, location, currencies, payment methods, and accounting workflow.&lt;/p&gt;
&lt;p&gt;Wise Business&lt;br&gt;
Wise Business provides invoicing and payment-request functionality for eligible business accounts. Its current documentation states that businesses can create invoices, select the currency, add customer and service details, and send the invoice directly to the customer. Payment methods available through an invoice depend on the account and business location.&lt;/p&gt;
&lt;p&gt;Wise also supports payment links. A business can create a payment link for a specified amount and currency, then share it with a customer. Wise states that single-use links can receive one payment, while reusable links can receive multiple payments.&lt;/p&gt;
&lt;p&gt;This can be useful for developers who work with international clients and need to receive money in supported currencies.&lt;/p&gt;
&lt;p&gt;However, developers should check Wise's current availability and fees for their particular country and account before promising a specific payment method to clients. Wise's documentation also shows that some card-payment features are restricted to particular regions and are unavailable to new customers in some circumstances.&lt;/p&gt;
&lt;p&gt;PayPal&lt;br&gt;
PayPal can provide another payment option for freelance developers whose clients already use PayPal.&lt;/p&gt;
&lt;p&gt;Its Payment Links system allows businesses to create shareable payment links without building a custom checkout system. According to PayPal's current developer documentation, payment links can be shared through email, text messages, social media, or a website.&lt;/p&gt;
&lt;p&gt;A developer could therefore create a payment request for a particular service and send the link to a client rather than asking the client to manually enter bank details.&lt;/p&gt;
&lt;p&gt;PayPal also provides an API for businesses that require greater technical control over payment links.&lt;/p&gt;
&lt;p&gt;That distinction matters for web developers. A developer who simply wants to collect project payments may only need the dashboard-based payment-link system. A developer building a custom client portal or application may have a reason to investigate API-based payment functionality.&lt;/p&gt;
&lt;p&gt;FreshBooks&lt;br&gt;
FreshBooks combines invoicing with online payment functionality. Its current payment documentation describes support for one-time invoices, recurring invoices, deposits, online payment options, and checkout links. FreshBooks Payments is powered by Stripe.&lt;/p&gt;
&lt;p&gt;FreshBooks also allows businesses to configure available payment options. Depending on the business location and account, supported methods can include cards, bank transfers, PayPal, and other options.&lt;/p&gt;
&lt;p&gt;This type of system can be particularly useful for developers who want invoicing and payment records connected rather than maintaining separate spreadsheets.&lt;/p&gt;
&lt;p&gt;Use Deposits for Larger Development Projects&lt;br&gt;
One of the most important payment practices for freelance developers is separating the project into payment stages.&lt;/p&gt;
&lt;p&gt;Instead of completing an entire website and requesting 100 percent of the money afterward, a developer can structure the contract around milestones.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;p&gt;Project total: $2,000&lt;/p&gt;
&lt;p&gt;40 percent deposit before development begins: $800&lt;br&gt;
30 percent after the agreed development milestone: $600&lt;br&gt;
30 percent before final delivery or deployment: $600&lt;br&gt;
The exact percentages should be determined by the agreement between the developer and client.&lt;/p&gt;
&lt;p&gt;The important point is that invoices should correspond with the agreed payment schedule.&lt;/p&gt;
&lt;p&gt;Invoicing software can make this easier because some platforms support deposits, recurring billing, and different billing arrangements. FreshBooks, for example, explicitly supports deposit-based and recurring invoices.&lt;/p&gt;
&lt;p&gt;Connect the Payment Method to the Invoice&lt;br&gt;
Sending an invoice and then asking the client to figure out how to pay creates unnecessary friction.&lt;/p&gt;
&lt;p&gt;A better system places the payment method directly inside the invoice.&lt;/p&gt;
&lt;p&gt;The client should be able to see:&lt;/p&gt;
&lt;p&gt;Amount due: $800&lt;br&gt;
Due date: September 30, 2026&lt;br&gt;
Payment method: Online payment&lt;br&gt;
Action: Pay invoice&lt;/p&gt;
&lt;p&gt;Payment links can provide another route.&lt;/p&gt;
&lt;p&gt;Wise allows businesses to create payment links and share them with customers. PayPal similarly allows businesses to create and distribute payment links through websites and communications channels.&lt;/p&gt;
&lt;p&gt;For a developer, this means a client does not necessarily need to navigate through multiple messages to determine where money should be sent.&lt;/p&gt;
&lt;p&gt;Use Different Payment Methods for Different Clients&lt;br&gt;
A freelance developer does not necessarily need to offer every payment method available.&lt;/p&gt;
&lt;p&gt;Instead, consider where the clients are located and how they normally pay.&lt;/p&gt;
&lt;p&gt;A local client may prefer a domestic bank transfer. An international client may prefer a card, PayPal, or another supported payment method.&lt;/p&gt;
&lt;p&gt;The objective is to provide practical payment options while controlling transaction costs and administrative complexity.&lt;/p&gt;
&lt;p&gt;Before enabling a payment method, check:&lt;/p&gt;
&lt;p&gt;Transaction fees&lt;br&gt;
Currency-conversion charges&lt;br&gt;
Settlement time&lt;br&gt;
Supported countries&lt;br&gt;
Supported currencies&lt;br&gt;
Refund procedures&lt;br&gt;
Chargeback rules&lt;br&gt;
Account eligibility&lt;br&gt;
Withdrawal options&lt;br&gt;
Do not advertise a payment method simply because a platform's website mentions it. Availability can depend on the developer's country, business registration, account status, currency, and other conditions.&lt;/p&gt;
&lt;p&gt;Keep Payment Records Separate From Client Conversations&lt;br&gt;
WhatsApp messages, email threads, and social-media conversations are poor substitutes for financial records.&lt;/p&gt;
&lt;p&gt;Every invoice should have a unique reference.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;p&gt;INV-2026-001&lt;br&gt;
INV-2026-002&lt;br&gt;
INV-2026-003&lt;/p&gt;
&lt;p&gt;When the client pays by bank transfer, the invoice number can also be used as a payment reference where the payment platform supports that workflow.&lt;/p&gt;
&lt;p&gt;Wise specifically instructs customers paying invoices by bank transfer to include the invoice number or unique payment reference in the bank's reference field.&lt;/p&gt;
&lt;p&gt;This makes reconciliation easier.&lt;/p&gt;
&lt;p&gt;A developer should also maintain a simple record showing:&lt;/p&gt;
&lt;p&gt;InvoiceClientAmountDue DateStatus&lt;br&gt;
INV-001Client A$800Sept. 25Paid&lt;br&gt;
INV-002Client B$1,200Sept. 30Pending&lt;br&gt;
INV-003Client C$500Oct. 5Draft&lt;br&gt;
The exact system can be a dedicated accounting platform, spreadsheet, or another appropriate financial-management tool.&lt;/p&gt;
&lt;p&gt;Automate Recurring Development Payments&lt;br&gt;
Many freelance developers eventually move beyond one-off projects.&lt;/p&gt;
&lt;p&gt;Website maintenance, security updates, content changes, technical support, hosting administration, and development retainers can generate recurring revenue.&lt;/p&gt;
&lt;p&gt;Instead of manually creating a new invoice every month, use recurring billing where the selected platform supports it.&lt;/p&gt;
&lt;p&gt;FreshBooks currently supports recurring invoices and recurring billing profiles, while its payment system can record payments against invoices.&lt;/p&gt;
&lt;p&gt;The developer still needs to monitor whether payments are successful and whether the client's contract remains active.&lt;/p&gt;
&lt;p&gt;Automation should reduce administrative work, not eliminate financial oversight.&lt;/p&gt;
&lt;p&gt;Add Payment Terms to the Contract&lt;br&gt;
An invoice should not be the only place where payment expectations appear.&lt;/p&gt;
&lt;p&gt;The freelance development agreement should establish the commercial terms before work begins.&lt;/p&gt;
&lt;p&gt;Important terms can include:&lt;/p&gt;
&lt;p&gt;Total project price&lt;br&gt;
Deposit requirement&lt;br&gt;
Milestone payments&lt;br&gt;
Final payment requirement&lt;br&gt;
Invoice due dates&lt;br&gt;
Late-payment provisions&lt;br&gt;
Revision limits&lt;br&gt;
Cancellation terms&lt;br&gt;
Refund terms&lt;br&gt;
Ownership or licensing conditions&lt;br&gt;
Hosting and maintenance charges&lt;br&gt;
Third-party software costs&lt;br&gt;
The invoice then implements those agreed terms.&lt;/p&gt;
&lt;p&gt;This distinction is important. An invoice records a financial demand for services already governed by an agreement. It should not unexpectedly introduce major commercial conditions after development has started.&lt;/p&gt;
&lt;p&gt;Do Not Ignore Transaction Fees&lt;br&gt;
Payment gateways are businesses. Processing payments can involve fees, and international payments can introduce additional costs.&lt;/p&gt;
&lt;p&gt;A developer charging $1,000 should know whether the payment platform deducts a processing fee, whether currency conversion applies, and when the funds become available.&lt;/p&gt;
&lt;p&gt;For example, FreshBooks states that its payment services have transaction fees and that rates and available methods depend on the service and eligibility.&lt;/p&gt;
&lt;p&gt;Wise also states that fees for payment links depend on how the customer pays.&lt;/p&gt;
&lt;p&gt;Therefore, do not calculate project profitability solely from the invoice's gross amount.&lt;/p&gt;
&lt;p&gt;A developer should track the amount invoiced, payment-processing costs, currency-conversion costs where applicable, taxes, and the final amount received.&lt;/p&gt;
&lt;p&gt;Use Payment Links on Your Website&lt;br&gt;
Payment links are not limited to invoices.&lt;/p&gt;
&lt;p&gt;A freelance developer can place an appropriate payment link on a service page, client portal, or other controlled part of a website.&lt;/p&gt;
&lt;p&gt;For example, a developer offering a fixed-price website audit could have a service page containing:&lt;/p&gt;
&lt;p&gt;Website Audit: $150&lt;/p&gt;
&lt;p&gt;The page can explain what the service includes and provide an appropriate payment option.&lt;/p&gt;
&lt;p&gt;Wise's documentation describes using an open payment link on a business website, while PayPal provides payment links that can be shared through websites and other channels.&lt;/p&gt;
&lt;p&gt;The developer should still make sure the payment description is clear. A payment button should not replace proper documentation for larger projects.&lt;/p&gt;
&lt;p&gt;Build a Simple Payment Workflow&lt;br&gt;
A freelance developer can reduce administrative problems by creating a standard workflow:&lt;/p&gt;
&lt;p&gt;Step 1: Agree on scope&lt;/p&gt;
&lt;p&gt;Define exactly what the client is purchasing.&lt;/p&gt;
&lt;p&gt;Step 2: Sign the agreement&lt;/p&gt;
&lt;p&gt;Confirm price, milestones, deadlines, revisions, and payment terms.&lt;/p&gt;
&lt;p&gt;Step 3: Create the invoice&lt;/p&gt;
&lt;p&gt;Assign a unique invoice number and include the agreed services.&lt;/p&gt;
&lt;p&gt;Step 4: Request the deposit&lt;/p&gt;
&lt;p&gt;Do not begin work until the agreed initial payment condition has been satisfied.&lt;/p&gt;
&lt;p&gt;Step 5: Track the invoice&lt;/p&gt;
&lt;p&gt;Record whether it is pending, partially paid, paid, overdue, or cancelled.&lt;/p&gt;
&lt;p&gt;Step 6: Send milestone invoices&lt;/p&gt;
&lt;p&gt;Bill according to the contract rather than relying on informal reminders.&lt;/p&gt;
&lt;p&gt;Step 7: Confirm payment&lt;/p&gt;
&lt;p&gt;Match the payment against the correct invoice.&lt;/p&gt;
&lt;p&gt;Step 8: Deliver the agreed work&lt;/p&gt;
&lt;p&gt;Follow the contractual delivery and acceptance process.&lt;/p&gt;
&lt;p&gt;Step 9: Maintain records&lt;/p&gt;
&lt;p&gt;Keep invoices, receipts, contracts, and payment records organized for accounting and tax purposes.&lt;/p&gt;
&lt;p&gt;Final Considerations for Freelance Developers&lt;br&gt;
Invoicing and payment gateways are not merely administrative conveniences. They are part of the infrastructure of a freelance development business.&lt;/p&gt;
&lt;p&gt;A professional system should make four things clear: what the client purchased, how much is owed, when payment is due, and how the client can pay.&lt;/p&gt;
&lt;p&gt;Tools such as Wise Business, PayPal, and FreshBooks provide different combinations of invoicing, payment links, online payments, and financial administration. Their capabilities and eligibility vary, so developers should verify current terms and availability before selecting a platform.&lt;/p&gt;
&lt;p&gt;For a developer starting with small projects, the setup can remain simple. Create professional invoices, require deposits where appropriate, provide practical payment options, assign invoice numbers, track outstanding balances, and record every payment.&lt;/p&gt;
&lt;p&gt;As the client base grows, recurring billing, automated reminders, payment links, accounting integrations, and more structured financial reporting can reduce the administrative workload.&lt;/p&gt;
&lt;p&gt;The goal is straightforward: complete the technical work, document the commercial arrangement, make payment easy for the client, and maintain accurate records from the first invoice to the final payment.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/27996/how-to-use-invoicing-and-payment-gateway-tools-for-freelance-web-developers</guid>
<pubDate>Mon, 21 Sep 2026 19:57:59 +0000</pubDate>
</item>
<item>
<title>You Are Not Selling Your Code. You Are Selling the Next Commit</title>
<link>https://coderlegion.com/26484/you-are-not-selling-your-code-you-are-selling-the-next-commit</link>
<description>&lt;p&gt;I spent the last few weeks reading the documentation, pricing pages and source of every tool I could find that sells access to a private GitHub repo. I expected the comparison to come down to features. It came down to one sentence in GitHub's own docs, and after that the shopping list looked completely different.&lt;/p&gt;
&lt;p&gt;Here is the sentence, from the page on removing an outside collaborator: &quot;While forks of private repositories are deleted when a collaborator is removed, the person will still retain any local clones of your repository.&quot; The next line tells you the rest of the job is yours - you are responsible for making sure the people who lost access delete what they copied.&lt;/p&gt;
&lt;p&gt;A &lt;code&gt;git clone&lt;/code&gt; takes ten seconds. No API call reaches it. So whatever you were told about protecting your source, the plain version is this: the only thing a refund can take back is the future.&lt;/p&gt;
&lt;h3 id=&quot;revocation-is-a-subscription-feature-not-a-security-feature&quot;&gt;Revocation is a subscription feature, not a security feature&lt;/h3&gt;
&lt;p&gt;Once you accept that, the whole category re-sorts itself. Revocation is not there to protect your code. It is there to keep a refunded buyer or a charged-back card away from the next tag, the fixed bug, the private issue tracker, the room where the other buyers talk.&lt;/p&gt;
&lt;p&gt;That is a boring plumbing job, and boring plumbing is exactly what you should be shopping for. It also explains the refund policies of the people already doing this at scale: several of the big commercial boilerplates simply say access, once redeemed, is not returnable. The one I found with a proper no-questions window ships a generated download rather than repo access. A refund window is cheap when the product is a snapshot.&lt;/p&gt;
&lt;p&gt;There is a pricing consequence hiding in there too. If you sell yearly access and your tool cannot revoke on its own, you are selling lifetime access at a yearly price, whatever the checkout page says.&lt;/p&gt;
&lt;h3 id=&quot;three-things-decide-this-before-any-tool-does&quot;&gt;Three things decide this before any tool does&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;GitHub's own rules come first.&lt;/strong&gt; A repo under a personal account can only take collaborator invites; team-based delivery needs an organization. Organizations on a paid plan bill per seat, and a pending invitation is already a seat. Half the tools out there work one way, half the other, and that single fact eliminates most of the list before you have compared a single feature.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Then the invitation itself, which is where sellers actually lose money.&lt;/strong&gt; GitHub emails the invite to the buyer's primary address, not the address they paid with, and it expires after seven days. There is no page on GitHub where a buyer can see invitations waiting for them - the community request for one was opened in 2018 and people were still adding &quot;any update?&quot; to the thread last year.&lt;/p&gt;
&lt;p&gt;So your real timeline is: payment, an email into an inbox you do not control, six days of silence, expiry, a 404 that reads to the buyer as &quot;I paid and got nothing&quot;, and a support ticket on day eight. Exactly one tool I looked at treats that as its own problem and re-issues before the deadline. Every other tool, and every tutorial script, leaves you as the retry mechanism.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Then geography and who is merchant of record.&lt;/strong&gt; Stripe's availability page lists 51 countries and regions, and seven carry an asterisk: India and Indonesia in preview, five African countries handed to Paystack. That leaves 44 where you simply sign up, against 193 UN members. If you are outside that list, the entire &quot;just connect Stripe&quot; branch is closed to you and the question becomes which merchant of record will take you. If you are inside it, you get to choose whether the sales tax work is yours or somebody else's, and that choice costs a few points per sale.&lt;/p&gt;
&lt;h3 id=&quot;every-option-that-exists-in-one-breath&quot;&gt;Every option that exists, in one breath&lt;/h3&gt;
&lt;p&gt;Hosted, nothing to run: Polar has repo access as a built-in benefit, Dodo Payments is the newest merchant of record with a GitHub entitlement, and GitHub Sponsors will do sponsors-only repos at zero percent if monthly billing and an open-source-companion shape fit you.&lt;/p&gt;
&lt;p&gt;Storefronts where you stay the merchant: BuyMyRepo, GitPaywall, Anystack. Crypto checkout with tarballs instead of invites: gitbuyer.&lt;/p&gt;
&lt;p&gt;Payment platforms with no GitHub feature at all, where you bring your own glue: Gumroad, Lemon Squeezy, Whop, Patreon, Ko-fi, Buy Me a Coffee. The glue is usually Zapier, Make or n8n, and the recipes you will find online reliably cover the invite and quietly skip the revoke.&lt;/p&gt;
&lt;p&gt;Self-hosted: Honorbox, Pay2Clone, SellRepo, Lemonrepo, and RepoAccess, which is mine - I will come back to that. Plus a long tail of $7 and $20 scripts and tutorials, which is where most sellers actually start.&lt;/p&gt;
&lt;p&gt;And the option nobody frames as an option: skip repo access entirely. Ship a zip behind a license key. If your product is a snapshot rather than a stream of updates, the whole invite lifecycle is a cost with no benefit.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Where the tool sits              &lt;/th&gt;&lt;th&gt;Examples                                                           &lt;/th&gt;&lt;th&gt;Revokes without you&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Hosted, built-in GitHub delivery &lt;/td&gt;&lt;td&gt;Polar, Dodo Payments, GitHub Sponsors                              &lt;/td&gt;&lt;td&gt;Mostly yes&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Self-hosted                      &lt;/td&gt;&lt;td&gt;Honorbox, Pay2Clone, SellRepo, Lemonrepo, RepoAccess               &lt;/td&gt;&lt;td&gt;Depends, and the differences are large&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Platform plus glue               &lt;/td&gt;&lt;td&gt;Gumroad, Lemon Squeezy, Whop and friends, wired with Zapier or n8n &lt;/td&gt;&lt;td&gt;Usually no&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&quot;mine-has-a-gap-and-here-it-is&quot;&gt;Mine has a gap, and here it is&lt;/h3&gt;
&lt;p&gt;I built one of these. RepoAccess grants a GitHub team invite when a payment webhook arrives and removes it on refunds and chargebacks, and it runs as a single Cloudflare Worker on your own account.&lt;/p&gt;
&lt;p&gt;It does not re-issue an invitation before the seven-day expiry. Honorbox does, at day six, up to three times, reading GitHub's own expiry flag rather than trusting a calendar. That is a real gap, I am naming it in my own comparison rather than hiding it, and it is on my list.&lt;/p&gt;
&lt;h3 id=&quot;the-two-minute-test&quot;&gt;The two-minute test&lt;/h3&gt;
&lt;p&gt;Whatever you end up choosing, do this before your first sale: issue a refund in test mode, then open your organization's People page.&lt;/p&gt;
&lt;p&gt;If the buyer is still sitting there, the tool you bought is a granting tool, and the revoking is you.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;The long version - every tool with its actual behaviour, the fee table, what a month of twenty sales costs on each path, and six questions that each end in a name - is here: &lt;a rel=&quot;nofollow&quot; href=&quot;https://edgekits.dev/en/blog/sell-access-private-github-repo-every-option-compared/&quot;&gt;How to Sell Access to a Private GitHub Repo: Every Option Compared&lt;/a&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/26484/you-are-not-selling-your-code-you-are-selling-the-next-commit</guid>
<pubDate>Tue, 08 Sep 2026 11:35:30 +0000</pubDate>
</item>
<item>
<title># We Built a Safer Bitcoin Payment Layer for Zorgax — Here's What We Learned</title>
<link>https://coderlegion.com/25742/we-built-a-safer-bitcoin-payment-layer-for-zorgax-heres-what-we-learned</link>
<description>&lt;h2 id=&quot;we-built-a-safer-bitcoin-payment-layer-for-zorgax-here-s-what-we-learned&quot;&gt;We Built a Safer Bitcoin Payment Layer for Zorgax — Here's What We Learned&lt;/h2&gt;
&lt;p&gt;Adding Bitcoin to an application sounds simple.&lt;/p&gt;
&lt;p&gt;Generate an address, wait for a transaction, verify the amount, unlock the service.&lt;/p&gt;
&lt;p&gt;In a prototype, that might be enough.&lt;/p&gt;
&lt;p&gt;In a system designed around AI agents and autonomous infrastructure, it isn't.&lt;/p&gt;
&lt;p&gt;We've been working on the Bitcoin payment layer for &lt;strong&gt;Zorgax&lt;/strong&gt;, the AI and knowledge component of the MyZubster ecosystem.&lt;/p&gt;
&lt;p&gt;The latest implementation milestone is now complete locally:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;60 test suites. 208 tests. All passing.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;But the more interesting part isn't the test count.&lt;/p&gt;
&lt;p&gt;It's what we had to design around.&lt;/p&gt;
&lt;h3 id=&quot;one-checkout-one-bitcoin-address&quot;&gt;One Checkout, One Bitcoin Address&lt;/h3&gt;
&lt;p&gt;The first architectural decision was to avoid using a single Bitcoin address for every payment.&lt;/p&gt;
&lt;p&gt;Each checkout intent receives a different address derived from an Electrum-compatible watch-only wallet.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Payment Intent #001
        ↓
    Address A

Payment Intent #002
        ↓
    Address B

Payment Intent #003
        ↓
    Address C
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This gives us a much cleaner relationship between an intent and its blockchain activity.&lt;/p&gt;
&lt;p&gt;Instead of asking:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Which customer sent this transaction?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;we already have a strong association between the destination address and the checkout.&lt;/p&gt;
&lt;p&gt;It also avoids unnecessarily reusing the same public address across unrelated payments.&lt;/p&gt;
&lt;h3 id=&quot;the-application-doesn-t-need-the-spending-keys&quot;&gt;The Application Doesn't Need the Spending Keys&lt;/h3&gt;
&lt;p&gt;A payment service needs to observe money.&lt;/p&gt;
&lt;p&gt;It doesn't necessarily need permission to spend it.&lt;/p&gt;
&lt;p&gt;That's why the architecture uses a &lt;strong&gt;watch-only boundary&lt;/strong&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;             ZORGAX

                ↓

        Watch-Only Wallet

                ↓

              XPUB

                ↓

       Address Derivation

                ↓

       Blockchain Monitoring
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The application can derive and monitor addresses without holding the corresponding private spending keys.&lt;/p&gt;
&lt;p&gt;That's an important security boundary.&lt;/p&gt;
&lt;p&gt;If the web application doesn't need the capability to spend funds, we shouldn't give it that capability.&lt;/p&gt;
&lt;h3 id=&quot;bitcoin-amounts-are-integers&quot;&gt;Bitcoin Amounts Are Integers&lt;/h3&gt;
&lt;p&gt;Another rule: don't make financial correctness depend on floating-point arithmetic.&lt;/p&gt;
&lt;p&gt;Bitcoin gives us a convenient smallest unit:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;1 BTC = 100,000,000 satoshis
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So internally we verify integer satoshi values.&lt;/p&gt;
&lt;p&gt;Instead of reasoning about:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;0.00125000 BTC
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;the system can reason about:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;125000 sats
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Expected amount:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;125000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Received amount:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;125000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Comparison:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;125000 == 125000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Boring?&lt;/p&gt;
&lt;p&gt;Absolutely.&lt;/p&gt;
&lt;p&gt;That's exactly what I want from payment arithmetic.&lt;/p&gt;
&lt;h3 id=&quot;distributed-systems-retry-everything&quot;&gt;Distributed Systems Retry Everything&lt;/h3&gt;
&lt;p&gt;Then we reach one of the more important problems.&lt;/p&gt;
&lt;p&gt;Imagine the blockchain watcher detects a transaction.&lt;/p&gt;
&lt;p&gt;It sends the event to the application.&lt;/p&gt;
&lt;p&gt;The application processes it successfully.&lt;/p&gt;
&lt;p&gt;But the acknowledgement gets lost.&lt;/p&gt;
&lt;p&gt;The watcher retries.&lt;/p&gt;
&lt;p&gt;Without protection:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;TX detected
    ↓
Payment processed
    ↓
Network problem
    ↓
Retry
    ↓
Payment processed AGAIN
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That's unacceptable.&lt;/p&gt;
&lt;p&gt;So transaction processing needs to be idempotent.&lt;/p&gt;
&lt;p&gt;The TXID becomes part of that boundary.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Incoming TXID
     ↓
Already processed?
   ↙             ↘
 YES             NO
  ↓               ↓
Stop            Verify
                  ↓
                Record
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The same blockchain transaction should not create multiple economic effects because a distributed system retried a message.&lt;/p&gt;
&lt;h3 id=&quot;valid-doesn-t-mean-valid-everywhere&quot;&gt;Valid Doesn't Mean Valid Everywhere&lt;/h3&gt;
&lt;p&gt;There's another subtle distinction.&lt;/p&gt;
&lt;p&gt;A real Bitcoin transaction can be perfectly valid on-chain and still be invalid for a particular checkout.&lt;/p&gt;
&lt;p&gt;That's why we also need anti-replay protections.&lt;/p&gt;
&lt;p&gt;Payment verification isn't simply:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Is this TX valid?
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It is closer to:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Does this transaction
        ↓
belong to this payment intent
        ↓
for this derived address
        ↓
for this expected amount
        ↓
and has it already been consumed?
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The transaction has context.&lt;/p&gt;
&lt;p&gt;That's especially important once machines start consuming payment APIs automatically.&lt;/p&gt;
&lt;h3 id=&quot;database-state-is-part-of-the-deployment&quot;&gt;Database State Is Part of the Deployment&lt;/h3&gt;
&lt;p&gt;Payment systems don't live entirely inside application code.&lt;/p&gt;
&lt;p&gt;Adding new payment behavior changes persisted state too.&lt;/p&gt;
&lt;p&gt;For Zorgax, that means MongoDB migration needs to be treated as an explicit deployment operation.&lt;/p&gt;
&lt;p&gt;Not:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;git push
   ↓
hope
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Code
 ↓
Tests
 ↓
Review
 ↓
Migration Plan
 ↓
Infrastructure Check
 ↓
Database Migration
 ↓
Deployment
 ↓
Enable Rail
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The migration should be controlled independently from writing the feature.&lt;/p&gt;
&lt;h3 id=&quot;feature-flags-are-security-features-too&quot;&gt;Feature Flags Are Security Features Too&lt;/h3&gt;
&lt;p&gt;The Bitcoin rail currently remains disabled.&lt;/p&gt;
&lt;p&gt;Not because the implementation hasn't been tested.&lt;/p&gt;
&lt;p&gt;Because the surrounding production infrastructure isn't ready yet.&lt;/p&gt;
&lt;p&gt;The code still needs production dependencies such as the watch-only wallet XPUB and a reachable Electrum boundary through appropriately protected infrastructure.&lt;/p&gt;
&lt;p&gt;This gives us an important rule:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;CODE COMPLETE

     ≠

PRODUCTION READY
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A payment feature shouldn't become active just because somebody merged the code.&lt;/p&gt;
&lt;p&gt;Infrastructure readiness is part of feature readiness.&lt;/p&gt;
&lt;h3 id=&quot;where-we-are-now&quot;&gt;Where We Are Now&lt;/h3&gt;
&lt;p&gt;The current local milestone includes per-intent Electrum watch-only addresses, anti-replay protection, TXID idempotency, integer satoshi verification and a controlled MongoDB migration path.&lt;/p&gt;
&lt;p&gt;The implementation currently passes:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;60 test suites
208 tests
──────────────
ALL PASSING
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The generated patch was also checked using:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git apply --check
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But no production commit, push, deployment, real database migration or Bitcoin transaction was performed as part of this milestone.&lt;/p&gt;
&lt;p&gt;That distinction matters.&lt;/p&gt;
&lt;h3 id=&quot;why-are-we-doing-this-inside-an-ai-project-&quot;&gt;Why Are We Doing This Inside an AI Project?&lt;/h3&gt;
&lt;p&gt;Because Zorgax isn't intended to remain only a chatbot.&lt;/p&gt;
&lt;p&gt;We're exploring what happens when AI systems become capable of interacting with services and infrastructure.&lt;/p&gt;
&lt;p&gt;Eventually an agent might need to purchase something.&lt;/p&gt;
&lt;p&gt;Maybe compute.&lt;/p&gt;
&lt;p&gt;Maybe an API request.&lt;/p&gt;
&lt;p&gt;Maybe access to a specialized dataset.&lt;/p&gt;
&lt;p&gt;Maybe a physical service.&lt;/p&gt;
&lt;p&gt;The workflow starts looking like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;AI Agent
   ↓
Needs Resource
   ↓
Requests Service
   ↓
Receives Payment Intent
   ↓
Payment
   ↓
Verification
   ↓
Resource Unlocked
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now the payment layer becomes part of agent architecture.&lt;/p&gt;
&lt;h3 id=&quot;machine-payments-change-the-threat-model&quot;&gt;Machine Payments Change the Threat Model&lt;/h3&gt;
&lt;p&gt;Humans are relatively slow.&lt;/p&gt;
&lt;p&gt;Agents aren't.&lt;/p&gt;
&lt;p&gt;An automated agent could make hundreds of requests, retry failed operations and coordinate multiple services simultaneously.&lt;/p&gt;
&lt;p&gt;That means payment infrastructure designed for agents has to assume automation from the beginning.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;          AI AGENT
              ↓
       Payment Intent
              ↓
      Per-Intent Address
              ↓
            BTC
              ↓
           Electrum
              ↓
     Blockchain Detection
              ↓
       Verification Layer
        ↙      ↓       ↘
    Amount    TXID    Intent
      ↓        ↓        ↓
    sats   idempotent  replay
              ↓
        Payment State
              ↓
          SERVICE
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is where payments stop being just a UI problem.&lt;/p&gt;
&lt;p&gt;They become a distributed-systems problem.&lt;/p&gt;
&lt;h3 id=&quot;the-bigger-experiment&quot;&gt;The Bigger Experiment&lt;/h3&gt;
&lt;p&gt;Bitcoin checkout is useful by itself.&lt;/p&gt;
&lt;p&gt;But we're interested in what comes after it.&lt;/p&gt;
&lt;p&gt;Imagine autonomous software where:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Agent A
   ↓
discovers
   ↓
Service B
   ↓
requests price
   ↓
Payment Intent
   ↓
settlement
   ↓
verification
   ↓
service delivered
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;No shopping cart.&lt;/p&gt;
&lt;p&gt;No human clicking &quot;Pay Now.&quot;&lt;/p&gt;
&lt;p&gt;Potentially no traditional account relationship between the two systems.&lt;/p&gt;
&lt;p&gt;Just protocols.&lt;/p&gt;
&lt;p&gt;That creates a much larger engineering question:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What does payment infrastructure look like when the customer is software?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;We don't have the complete answer yet.&lt;/p&gt;
&lt;p&gt;But building the boring pieces correctly — integer amounts, idempotency, replay protection, isolated payment intents, watch-only wallets and controlled migrations — is probably where it starts.&lt;/p&gt;
&lt;p&gt;Because autonomous payments won't become reliable by making agents smarter.&lt;/p&gt;
&lt;p&gt;They'll become reliable by making the infrastructure underneath them predictable.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;If you were designing payments specifically for autonomous AI agents, what would you prioritize first: Bitcoin, Lightning, stablecoins, traditional APIs, or protocol-native payments?&lt;/strong&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/25742/we-built-a-safer-bitcoin-payment-layer-for-zorgax-heres-what-we-learned</guid>
<pubDate>Sat, 29 Aug 2026 06:45:23 +0000</pubDate>
</item>
<item>
<title>Stripe vs 10 Alternatives: Which One Is Right for Your Business? (2026)</title>
<link>https://coderlegion.com/24013/stripe-vs-10-alternatives-which-one-is-right-for-your-business-2026</link>
<description>&lt;p&gt;You've decided to accept payments on your app. Naturally, you reach for Stripe — everyone does. But then you notice the 2.9% + 30¢ fee, or you realize you're selling to customers in 40 countries and have no idea how sales tax works in each one, or your client specifically asked for PayPal because &quot;that's what their customers trust.&quot;&lt;/p&gt;
&lt;p&gt;Suddenly &quot;just use Stripe&quot; isn't such an easy call anymore.&lt;/p&gt;
&lt;p&gt;Stripe is the default answer for a reason — great docs, clean API, huge ecosystem. But it's not always the &lt;em&gt;right&lt;/em&gt; answer. Some businesses need lower fees at scale, some need someone else to handle global tax compliance for them, some need better support for a specific region, and some just need something simpler than Stripe's flexibility gives them.&lt;/p&gt;
&lt;p&gt;By the end of this guide, you'll know exactly how Stripe stacks up against 10 real alternatives — what each one costs, who it's actually built for, and which one fits your specific situation.&lt;/p&gt;
&lt;p&gt;Worth noting upfront: since acquiring Lemon Squeezy, Stripe has also entered the Merchant of Record space with &lt;strong&gt;Stripe Managed Payments&lt;/strong&gt;, making this comparison even more interesting in 2026 — Stripe is now competing with its own alternatives on this list. More on that later.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;why-this-comparison-is-tricky&quot;&gt;Why This Comparison Is Tricky&lt;/h3&gt;
&lt;p&gt;Payment processors aren't one thing. They fall into a few different categories, and comparing them apples-to-apples only works within a category:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Payment gateways / processors&lt;/strong&gt; (Stripe, Square, Braintree, Authorize.net) — you're the merchant of record, you handle your own tax compliance, they just move the money.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Merchant of Record (MoR) platforms&lt;/strong&gt; (Paddle, Lemon Squeezy) — &lt;em&gt;they&lt;/em&gt; become the legal seller, they handle sales tax and VAT for you, and they take a bigger cut for it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Wallet-first checkout&lt;/strong&gt; (PayPal) — optimized for buyer trust and one-click checkout rather than developer flexibility.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Enterprise-grade processors&lt;/strong&gt; (Adyen, Checkout.com) — built for large-scale, multi-region businesses with complex routing needs.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Regional specialists&lt;/strong&gt; (Razorpay, Mollie) — dominant in specific geographies where global players are weaker.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Keep this in mind as you read — &quot;cheaper than Stripe&quot; doesn't matter if the platform doesn't fit your business model in the first place.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;stripe-the-baseline&quot;&gt;Stripe: The Baseline&lt;/h3&gt;
&lt;p&gt;Before comparing anything, here's what you're actually comparing against.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Pricing:&lt;/strong&gt; 2.9% + 30¢ per successful card charge (US), with volume discounts available on custom plans&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Best for:&lt;/strong&gt; SaaS products, developer-first products, subscriptions, marketplaces&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Strengths:&lt;/strong&gt; Best-in-class docs, huge library ecosystem, Stripe Checkout/Elements/Payment Element for pre-built UI, strong webhook system, excellent for Next.js/React apps&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Weaknesses:&lt;/strong&gt; You're the merchant of record — you handle your own sales tax and VAT (Stripe Tax helps calculate it, but you still remit it), support can be slow for smaller accounts, standard fees aren't the cheapest at scale&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;&lt;p&gt;💡 &lt;strong&gt;Tip:&lt;/strong&gt; If you've used Stripe before, you already know most of the vocabulary in this post — Checkout Sessions, webhooks, Payment Intents. Every alternative below borrows the same mental model to some degree.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 id=&quot;who-should-not-use-stripe-&quot;&gt;Who Should NOT Use Stripe?&lt;/h4&gt;
&lt;p&gt;Stripe isn't the right fit for everyone. Skip it (or at least don't use it alone) if:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;You sell digital products globally and don't want to deal with tax registration&lt;/strong&gt; — Stripe Tax calculates VAT/sales tax, but you're still the one registering and remitting it in every country. A Merchant of Record like Paddle removes that burden entirely.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Your customers are mostly in India&lt;/strong&gt; — Stripe's support for UPI and local Indian payment rails is weaker than Razorpay's.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;You need in-person, physical card readers&lt;/strong&gt; — Stripe has Terminal hardware, but Square's combined online + in-person experience is more mature.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;You're processing enterprise-scale volume across many countries&lt;/strong&gt; — at that scale, Adyen or Checkout.com's negotiated interchange-plus pricing usually beats Stripe's standard rates.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Your audience specifically expects PayPal or Venmo at checkout&lt;/strong&gt; — Stripe supports some wallets, but Braintree's PayPal/Venmo integration is more native.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;1-paypal&quot;&gt;1. PayPal&lt;/h3&gt;
&lt;p&gt;The one every non-technical customer already recognizes.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Pricing:&lt;/strong&gt; ~2.99% + fixed fee per transaction (varies by country), similar ballpark to Stripe&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Best for:&lt;/strong&gt; E-commerce stores where buyer trust and one-click checkout matter more than API elegance&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Strengths:&lt;/strong&gt; Massive brand recognition — some customers only buy if PayPal is an option, built-in buyer/seller protection, works well for one-off purchases&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Weaknesses:&lt;/strong&gt; Developer experience is noticeably behind Stripe, account holds and freezes are a common complaint, less flexible for subscription billing logic&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;&lt;p&gt;⚠️ &lt;strong&gt;Common Mistake:&lt;/strong&gt; Choosing PayPal &lt;em&gt;instead of&lt;/em&gt; Stripe. Most stores that succeed with PayPal actually offer both — PayPal as a trust-building option alongside a primary Stripe or Square integration, not as a replacement.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h3 id=&quot;2-square&quot;&gt;2. Square&lt;/h3&gt;
&lt;p&gt;Best known for physical point-of-sale, but its online payments API has matured a lot.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Pricing:&lt;/strong&gt; 2.6% + 10¢ for in-person, ~2.9% + 30¢ for online — comparable to Stripe&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Businesses that need both online &lt;em&gt;and&lt;/em&gt; in-person payments under one system (restaurants, retail, service businesses)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Strengths:&lt;/strong&gt; Unified dashboard for online + POS + invoicing, no monthly fees on the base plan, solid inventory and appointment tools bundled in&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Weaknesses:&lt;/strong&gt; Online-only developer experience is less polished than Stripe's, less suited to complex SaaS billing logic&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Choose Square over Stripe if:&lt;/strong&gt; you also need physical card readers or in-person sales alongside your website.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;3-braintree-a-paypal-company-&quot;&gt;3. Braintree (a PayPal company)&lt;/h3&gt;
&lt;p&gt;Braintree is essentially &quot;Stripe for people who also want PayPal and Venmo in one integration.&quot;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Pricing:&lt;/strong&gt; 2.59% + 49¢ typically — slightly cheaper per-transaction than Stripe on paper&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Apps that want PayPal, Venmo, and card payments through a single SDK&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Strengths:&lt;/strong&gt; Owned by PayPal, so PayPal/Venmo integration is native rather than bolted on, solid mobile SDKs (iOS/Android)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Weaknesses:&lt;/strong&gt; Documentation and community support are noticeably smaller than Stripe's, fewer prebuilt UI components&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Choose Braintree over Stripe if:&lt;/strong&gt; your audience skews toward PayPal/Venmo as a primary payment method (common in US consumer apps).&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;4-adyen&quot;&gt;4. Adyen&lt;/h3&gt;
&lt;p&gt;The processor institutional fintechs and large marketplaces actually use.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Pricing:&lt;/strong&gt; Interchange-plus pricing (you pay the real card network fee + a small Adyen markup) — cheaper at high volume, but not built for small businesses&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Large-scale, multi-country businesses processing serious volume (Uber, Spotify, eBay-scale)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Strengths:&lt;/strong&gt; Single platform for 40+ payment methods across regions, excellent fraud detection (RevenueProtect), true global reach&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Weaknesses:&lt;/strong&gt; Not self-serve — you talk to a sales team, overkill (and often not even available) for early-stage products&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Choose Adyen over Stripe if:&lt;/strong&gt; you're already processing millions per month across multiple countries and need custom pricing.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;5-authorize-net&quot;&gt;5. Authorize.net&lt;/h3&gt;
&lt;p&gt;The processor that's been around since the dot-com era and quietly still works fine.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Pricing:&lt;/strong&gt; $25/month gateway fee + 2.9% + 30¢ per transaction — more expensive than Stripe once you add the monthly fee&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Traditional businesses already on legacy e-commerce platforms (older WooCommerce/Magento setups)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Strengths:&lt;/strong&gt; Extremely stable, works with a huge number of existing shopping cart integrations&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Weaknesses:&lt;/strong&gt; Developer experience feels dated compared to Stripe, extra monthly fee makes it a worse deal for low-volume stores&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Choose Authorize.net over Stripe if:&lt;/strong&gt; you're integrating into an existing legacy e-commerce stack that already expects it.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;6-paddle&quot;&gt;6. Paddle&lt;/h3&gt;
&lt;p&gt;A true Merchant of Record — Paddle becomes the legal seller so you don't have to.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Pricing:&lt;/strong&gt; ~5% + 50¢ per transaction (no separate monthly fee) — higher than Stripe, but that fee includes global tax handling&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Best for:&lt;/strong&gt; SaaS companies selling internationally who don't want to deal with VAT/sales tax registration in every country&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Strengths:&lt;/strong&gt; Handles sales tax, VAT, and invoicing compliance automatically, built specifically for SaaS subscription billing, strong checkout localization (currency, language)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Weaknesses:&lt;/strong&gt; Noticeably higher fees than a raw payment processor, less low-level control over the checkout flow&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;&lt;p&gt;💡 &lt;strong&gt;Tip:&lt;/strong&gt; The right way to think about the extra ~2% over Stripe is: you're paying Paddle to be your tax accountant and legal seller in every country. For a solo founder, that trade is often worth it.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;Where Stripe Managed Payments fits in:&lt;/strong&gt; since acquiring Lemon Squeezy, Stripe has been building its own Merchant of Record product — Stripe Managed Payments — which entered public preview in early 2026. It's Stripe's answer to exactly what Paddle already does: Stripe becomes the seller of record and handles global tax for you, at a similar ~5% fee. Paddle still has the longer track record and a more mature product for now, but this is a space to watch — you may eventually be able to get MoR-style tax handling without leaving the Stripe ecosystem at all.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;7-razorpay&quot;&gt;7. Razorpay&lt;/h3&gt;
&lt;p&gt;The dominant payment processor for businesses operating in India.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Pricing:&lt;/strong&gt; ~2% per transaction domestically, higher for international cards — meaningfully cheaper than Stripe &lt;em&gt;within India&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Businesses primarily serving Indian customers (UPI, Indian net banking, RuPay cards)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Strengths:&lt;/strong&gt; Deep support for local payment methods Stripe doesn't handle well (UPI, India-specific wallets), strong local compliance and support&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Weaknesses:&lt;/strong&gt; Not the right choice if your customers are mostly outside India, less relevant for Western SaaS products&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Choose Razorpay over Stripe if:&lt;/strong&gt; a meaningful share of your revenue comes from Indian customers.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;8-mollie&quot;&gt;8. Mollie&lt;/h3&gt;
&lt;p&gt;A European-focused processor that's become the default for a lot of EU-based SaaS and e-commerce.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Pricing:&lt;/strong&gt; Pay-per-transaction with no monthly fees, competitive with Stripe in the EU&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Businesses primarily serving European customers who want strong support for local payment methods (iDEAL, Bancontact, SOFORT)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Strengths:&lt;/strong&gt; Excellent support for European-specific payment rails Stripe supports less natively, simple, clean API&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Weaknesses:&lt;/strong&gt; Smaller ecosystem and community outside Europe, fewer advanced billing features than Stripe Billing&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Choose Mollie over Stripe if:&lt;/strong&gt; your customers are mostly European and rely on local bank-transfer-style payment methods.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;9-checkout-com&quot;&gt;9. Checkout.com&lt;/h3&gt;
&lt;p&gt;An enterprise-grade processor competing directly with Adyen for large global merchants.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Pricing:&lt;/strong&gt; Custom, interchange-plus — negotiated per contract, not published&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Large enterprises and marketplaces with global, high-volume payment needs&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Strengths:&lt;/strong&gt; Strong presence in Europe/Middle East, excellent uptime and fraud tooling, flexible routing for high-risk industries&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Weaknesses:&lt;/strong&gt; Sales-led onboarding, not something you sign up for and integrate in an afternoon — not built for indie developers or early-stage startups&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Choose Checkout.com over Stripe if:&lt;/strong&gt; you're an enterprise processing serious volume and need a dedicated account manager and custom terms.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;10-lemon-squeezy&quot;&gt;10. Lemon Squeezy&lt;/h3&gt;
&lt;p&gt;Worth including, with an important caveat: &lt;strong&gt;Stripe acquired Lemon Squeezy in 2024&lt;/strong&gt;, and in 2026 Stripe is actively rolling out &quot;Stripe Managed Payments&quot; — its own native Merchant of Record product built using what it learned from that acquisition.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Pricing:&lt;/strong&gt; 5% + 50¢ per transaction, no monthly fee&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Indie SaaS founders and digital product creators who want MoR-style tax handling with a simpler, more creator-focused feature set (affiliates, license keys, digital delivery) than Paddle offers&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Strengths:&lt;/strong&gt; Very fast to set up, built specifically for solo founders and small teams, handles global tax compliance like Paddle does&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Weaknesses:&lt;/strong&gt; Its long-term roadmap is now tied to Stripe's own Managed Payments product, so some indie-specific features may not carry over if you're forced to migrate down the line&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;&lt;p&gt;⚠️ &lt;strong&gt;Common Mistake:&lt;/strong&gt; Assuming Lemon Squeezy is a fully independent alternative to Stripe. Since the acquisition, it's best thought of as &quot;Stripe's on-ramp into the Merchant of Record space&quot; rather than a true competitor — worth knowing before you build a long-term dependency on it.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h3 id=&quot;quick-comparison-table&quot;&gt;Quick Comparison Table&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Platform &lt;/th&gt;&lt;th&gt;Merchant of Record? &lt;/th&gt;&lt;th&gt;Typical Fee &lt;/th&gt;&lt;th&gt;Best For&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Stripe&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;No &lt;/td&gt;&lt;td&gt;2.9% + 30¢ &lt;/td&gt;&lt;td&gt;Developer-first SaaS, subscriptions&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;PayPal&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;No &lt;/td&gt;&lt;td&gt;~2.99% + fee &lt;/td&gt;&lt;td&gt;Buyer trust, one-off e-commerce&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Square&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;No &lt;/td&gt;&lt;td&gt;2.9% + 30¢ &lt;/td&gt;&lt;td&gt;Online + in-person combined&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Braintree&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;No &lt;/td&gt;&lt;td&gt;2.59% + 49¢ &lt;/td&gt;&lt;td&gt;PayPal/Venmo-heavy audiences&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Adyen&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;No &lt;/td&gt;&lt;td&gt;Interchange-plus &lt;/td&gt;&lt;td&gt;Large-scale global enterprises&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Authorize.net&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;No &lt;/td&gt;&lt;td&gt;$25/mo + 2.9% + 30¢ &lt;/td&gt;&lt;td&gt;Legacy e-commerce stacks&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Paddle&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;Yes &lt;/td&gt;&lt;td&gt;~5% + 50¢ &lt;/td&gt;&lt;td&gt;Global SaaS avoiding tax registration&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Razorpay&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;No &lt;/td&gt;&lt;td&gt;~2% domestic &lt;/td&gt;&lt;td&gt;India-focused businesses&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Mollie&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;No &lt;/td&gt;&lt;td&gt;Pay-per-transaction &lt;/td&gt;&lt;td&gt;EU-focused businesses&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Checkout.com&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;No &lt;/td&gt;&lt;td&gt;Custom &lt;/td&gt;&lt;td&gt;Enterprise, high volume&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Lemon Squeezy&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;Yes &lt;/td&gt;&lt;td&gt;5% + 50¢ &lt;/td&gt;&lt;td&gt;Indie SaaS, digital products&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h3 id=&quot;how-to-actually-choose&quot;&gt;How to Actually Choose&lt;/h3&gt;
&lt;p&gt;Instead of picking based on fees alone, walk through these questions in order:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Do you want to handle sales tax/VAT yourself, or pay someone else to?&lt;/strong&gt; If you'd rather not deal with tax registration in every country you sell to, go with Paddle or Lemon Squeezy. If you're fine using Stripe Tax to calculate (and then remitting it yourself), stick with Stripe or another raw processor.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Where are your customers?&lt;/strong&gt; Mostly India → Razorpay. Mostly Europe → Mollie. Global and card-based → Stripe.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Do you need in-person payments too?&lt;/strong&gt; If yes, Square is the only one on this list that does both well out of the box.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Are you doing enterprise-scale volume?&lt;/strong&gt; If you're processing millions per month, Adyen or Checkout.com's custom pricing will beat any of the self-serve options.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Does your audience specifically expect PayPal?&lt;/strong&gt; Add it as a secondary option — don't make it your only processor for a SaaS product.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;For most Next.js SaaS builds, Stripe remains the safest default: best documentation, largest community, and the easiest to actually ship with. Reach for an alternative only when you have a specific, concrete reason — a region Stripe serves poorly, a tax burden you want off your plate, or a scale where custom pricing genuinely saves money.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;which-stripe-alternative-is-best-for-&quot;&gt;Which Stripe Alternative Is Best for...&lt;/h3&gt;
&lt;p&gt;Quick answers if you already know your use case and just want the pointer:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;...SaaS?&lt;/strong&gt; Stick with Stripe — it's still the most complete toolkit for subscription billing, and its ecosystem of libraries and guides is unmatched.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;...Subscriptions?&lt;/strong&gt; Stripe Billing is the most flexible for custom subscription logic. If you'd rather offload tax compliance too, Paddle is purpose-built for SaaS subscriptions specifically.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;...Marketplaces?&lt;/strong&gt; Stripe Connect is the standard here, but Adyen is worth evaluating once you're processing serious volume across many countries.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;...Digital Products?&lt;/strong&gt; Paddle or Lemon Squeezy — both act as Merchant of Record, which matters a lot for indie creators selling ebooks, courses, or software licenses globally.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;...Small Business?&lt;/strong&gt; Square, if you also sell in person. Stripe or PayPal if you're online-only and want the fastest setup.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;...Enterprise?&lt;/strong&gt; Adyen or Checkout.com — both offer negotiated pricing and dedicated support that self-serve platforms don't.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;...International Payments?&lt;/strong&gt; Mollie for Europe, Razorpay for India, Paddle if you want tax compliance handled everywhere at once.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;...Physical Stores?&lt;/strong&gt; Square, hands down — it's the only option on this list built for combined online + in-person payments from day one.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;frequently-asked-questions&quot;&gt;Frequently Asked Questions&lt;/h3&gt;
&lt;h4 id=&quot;is-stripe-still-the-best-choice-for-a-first-time-saas-product-&quot;&gt;Is Stripe still the best choice for a first-time SaaS product?&lt;/h4&gt;
&lt;p&gt;For most cases, yes. The documentation, community support, and prebuilt components (Checkout, Payment Element) make it the fastest path to actually shipping payments in a Next.js app.&lt;/p&gt;
&lt;h4 id=&quot;what-s-the-real-difference-between-a-payment-processor-and-a-merchant-of-record-&quot;&gt;What's the real difference between a payment processor and a Merchant of Record?&lt;/h4&gt;
&lt;p&gt;A processor (Stripe, Square, Braintree) just moves money — you're legally the seller and responsible for your own tax compliance. A Merchant of Record (Paddle, Lemon Squeezy) legally becomes the seller on your behalf, which means they handle VAT/sales tax registration and remittance for you, at a higher fee.&lt;/p&gt;
&lt;h4 id=&quot;can-i-use-more-than-one-payment-processor-at-once-&quot;&gt;Can I use more than one payment processor at once?&lt;/h4&gt;
&lt;p&gt;Yes, and many businesses do — for example, Stripe as the primary processor plus PayPal as a secondary option for customers who specifically prefer it. Just be aware this adds complexity to your webhook and reconciliation logic.&lt;/p&gt;
&lt;h4 id=&quot;are-paddle-and-lemon-squeezy-worth-the-extra-fees-for-a-solo-founder-&quot;&gt;Are Paddle and Lemon Squeezy worth the extra fees for a solo founder?&lt;/h4&gt;
&lt;p&gt;Often, yes. If you're a single developer selling globally, the ~2% extra fee can be far cheaper than the time (or accountant fees) it would take to register and file sales tax in dozens of countries yourself.&lt;/p&gt;
&lt;h4 id=&quot;does-switching-payment-processors-later-require-rebuilding-my-whole-app-&quot;&gt;Does switching payment processors later require rebuilding my whole app?&lt;/h4&gt;
&lt;p&gt;Not necessarily, if you've kept your payment logic reasonably isolated (a single &lt;code&gt;lib/payments&lt;/code&gt; module, for example) rather than scattering Stripe-specific calls across your codebase. It's still real migration work — expect to touch checkout flows, webhooks, and any stored customer/subscription IDs.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Choosing a payment processor isn't a one-time decision carved in stone — plenty of companies start with Stripe and add or switch processors as their business changes shape. The important thing is picking based on your actual constraints (tax burden, region, scale) rather than just going with whichever name is most familiar.&lt;/p&gt;
&lt;h3 id=&quot;continue-learning&quot;&gt;Continue Learning&lt;/h3&gt;
&lt;p&gt;If you've decided Stripe is still the right call (it usually is), these guides walk through the actual implementation in Next.js 16:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://devstacked.tech/blog/stripe-nextjs-16-guide&quot;&gt;How to Integrate Stripe Checkout with Next.js 16 (2026 Edition)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://devstacked.tech/blog/stripe-payment-element-nextjs-16-guide&quot;&gt;How to Integrate Stripe Payment Element with Next.js 16 (2026 Edition)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://devstacked.tech/blog/stripe-checkout-vs-payment-element&quot;&gt;Stripe Checkout vs Payment Element in 2026: Which One Should You Choose?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://devstacked.tech/blog/stripe-subscription-billing-nextjs-16&quot;&gt;Stripe Subscriptions in Next.js 16: Complete SaaS Billing Guide (2026)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/24013/stripe-vs-10-alternatives-which-one-is-right-for-your-business-2026</guid>
<pubDate>Mon, 03 Aug 2026 04:43:28 +0000</pubDate>
</item>
<item>
<title>Stripe Checkout vs Payment Element in 2026: Which One Should You Choose?</title>
<link>https://coderlegion.com/23293/stripe-checkout-vs-payment-element-in-2026-which-one-should-you-choose</link>
<description>&lt;p&gt;If you're planning to accept online payments with Stripe, one of the first decisions you'll need to make is how much of the checkout UI you want to build yourself versus let Stripe handle.&lt;/p&gt;
&lt;p&gt;Historically, developers framed this as &lt;strong&gt;Stripe Checkout vs Stripe Payment Element&lt;/strong&gt; — two separate products. As of 2026, Stripe has consolidated both under a single API: the &lt;strong&gt;Checkout Sessions API&lt;/strong&gt;, with three selectable UI modes. This guide has been updated to reflect that change.&lt;/p&gt;
&lt;p&gt;Many developers, especially beginners working with Next.js, still ask:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Which Stripe integration is easier?&lt;/li&gt;
&lt;li&gt;Should I use hosted Checkout, an embedded form, or a fully custom Elements page?&lt;/li&gt;
&lt;li&gt;Which one is better for SaaS products?&lt;/li&gt;
&lt;li&gt;Which option offers more customization?&lt;/li&gt;
&lt;li&gt;Can I switch later if my application grows?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In this guide, we'll compare the three UI modes, explain how each works, and help you decide which fits your project.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;2026-update-it-s-one-api-now-not-two-products&quot;&gt;2026 Update: It's One API Now, Not Two Products&lt;/h3&gt;
&lt;p&gt;Stripe's current official guidance:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;The &lt;code&gt;Checkout Sessions API&lt;/code&gt; is the recommended API for most developers. The &lt;code&gt;Payment Intents API&lt;/code&gt; is a lower-level API that you can use to build your own checkout or payments, but it requires significantly more code and ongoing maintenance. We recommend the Payment Element with Checkout Sessions for most integrations because it covers similar payment flows as Payment Intents.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In practice, this means every option below is created through the same call — &lt;code&gt;stripe.checkout.sessions.create()&lt;/code&gt; — with a different &lt;code&gt;ui_mode&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-txt&quot;&gt;checkout.sessions.create({ ui_mode: &quot;hosted_page&quot; })    → full-page redirect
checkout.sessions.create({ ui_mode: &quot;embedded_page&quot; })  → embedded form, no redirect
checkout.sessions.create({ ui_mode: &quot;elements&quot; })  → fully custom UI, most control
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is the framing shift worth internalizing: it's no longer &quot;Checkout vs Payment Element&quot; as two separate integrations — it's &lt;strong&gt;one API, three levels of control&lt;/strong&gt;, plus the older &quot;build a PaymentIntent and drop Payment Element on it yourself&quot; approach that still works but is now positioned as the manual, do-everything-yourself path rather than a default alternative.&lt;/p&gt;
&lt;p&gt;Stripe also provides Checkout Studio for configuring and optimizing Checkout experiences without building every part of the UI yourself.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;One of the biggest selling points of using a single &lt;code&gt;checkout.sessions.create()&lt;/code&gt; API is the backend simplicity. Regardless of which UI mode you choose, you only need to listen for a single webhook event: &lt;code&gt;checkout.session.completed&lt;/code&gt;. If you scales from Hosted to Elements later, your backend fulfillment logic doesn't need to change at all?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-three-ui-modes&quot;&gt;The Three UI Modes&lt;/h3&gt;
&lt;h4 id=&quot;1-hosted-formerly-stripe-checkout-&quot;&gt;1. Hosted (formerly &quot;Stripe Checkout&quot;)&lt;/h4&gt;
&lt;p&gt;A &lt;strong&gt;full-page payment experience&lt;/strong&gt;, either via redirect to a Stripe-hosted page or embedded full-page on your site.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-txt&quot;&gt;         Customer
            ↓
       Your Website
            ↓
Stripe Hosted Checkout Page
            ↓
    Payment Completed
            ↓
 Your Website Success Page
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Advantages&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Fastest integration — a Checkout Session and a redirect is often under an hour of work&lt;/li&gt;
&lt;li&gt;Stripe handles compliance, PCI burden is lowest here&lt;/li&gt;
&lt;li&gt;Built-in payment methods (cards, Apple Pay, Google Pay, Link, bank transfers, regional methods) with zero extra config&lt;/li&gt;
&lt;li&gt;Continuously optimized by Stripe for conversion and mobile&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Disadvantages&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Limited branding — colors/logo yes, layout no&lt;/li&gt;
&lt;li&gt;Customer technically leaves your page (even the &quot;embedded&quot; hosted variant is still Stripe's iframe content)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h4 id=&quot;2-embedded-the-middle-option-&quot;&gt;2. Embedded (the middle option)&lt;/h4&gt;
&lt;p&gt;An &lt;strong&gt;embedded form on your own page&lt;/strong&gt; — no redirect, but still Stripe-built and maintained UI, just meaningfully less code than Elements.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-txt&quot;&gt;        Customer
           ↓
      Your Website (embedded iframe)
           ↓
Stripe Processes Payment
           ↓
      Success Page (same site)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This fills the gap the old &quot;Checkout vs Payment Element&quot; framing treated as binary. If your only objection to hosted Checkout was &quot;the customer leaves my page,&quot; embedded mode solves that without taking on Elements-level implementation work.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; sidebars, modals, chat-based checkout, or anyone who wants &quot;stay on my site&quot; without owning the full payment form's state and validation.&lt;/p&gt;
&lt;p&gt;The established embedded mode (embedded_page) displays a full checkout form on your website, while the new 2026 preview (elements) provides smaller, modular components optimized for sidebars, chats, and modals.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;3-elements-formerly-stripe-payment-element-&quot;&gt;3. Elements (formerly &quot;Stripe Payment Element&quot;)&lt;/h4&gt;
&lt;p&gt;A &lt;strong&gt;fully custom payment form&lt;/strong&gt; built with Stripe's &lt;code&gt;PaymentElement&lt;/code&gt; component, giving you substantially more control over the layout, appearance, and checkout flow.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-txt&quot;&gt;        Customer
           ↓
      Your Website
           ↓
     Payment Element
           ↓
Stripe Processes Payment
           ↓
      Success Page
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Advantages&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Full control over layout, branding, UX, multi-step flows&lt;/li&gt;
&lt;li&gt;No external redirect&lt;/li&gt;
&lt;li&gt;Best fit for complex ecommerce, upsells, custom checkout logic&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Disadvantages&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;More development work — client secrets, form state, confirmation flow all on you&lt;/li&gt;
&lt;li&gt;More testing surface since the flow is fully custom&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;&lt;p&gt;💡 &lt;strong&gt;Note on implementation:&lt;/strong&gt; You can reach Elements-mode UI two ways — through &lt;code&gt;checkout.sessions.create({ ui_mode: &quot;elements&quot; })&lt;/code&gt; (the current recommended path, which still gives you Stripe-managed tax/discounts/subscription logic), or the older pattern of creating a raw &lt;code&gt;PaymentIntent&lt;/code&gt; and mounting &lt;code&gt;PaymentElement&lt;/code&gt; yourself. The older pattern still works and is what most existing tutorials (including our own &lt;a rel=&quot;nofollow&quot; href=&quot;https://devstacked.tech/blog/stripe-payment-element-nextjs-16-guide&quot;&gt;Payment Element implementation guide&lt;/a&gt;) show — but per Stripe's current docs, it's now the &quot;you rebuild tax/discounts/subscriptions yourself&quot; option, not a default equal to Checkout Sessions.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h3 id=&quot;feature-comparison&quot;&gt;Feature Comparison&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Feature                &lt;/th&gt;&lt;th&gt;Hosted &lt;/th&gt;&lt;th&gt;Embedded &lt;/th&gt;&lt;th&gt;Elements&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Setup Time              &lt;/td&gt;&lt;td&gt;⭐⭐⭐⭐⭐  &lt;/td&gt;&lt;td&gt;⭐⭐⭐⭐    &lt;/td&gt;&lt;td&gt;⭐⭐⭐&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Customization            &lt;/td&gt;&lt;td&gt;15 configurable settings&amp;lt;br/&amp;gt; via brand settings     &lt;/td&gt;&lt;td&gt;70 configurable settings&amp;lt;br/&amp;gt; via the Appearance API &lt;/td&gt;&lt;td&gt;Full CSS customization&amp;lt;br/&amp;gt; via the Appearance API&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Stripe-hosted page         &lt;/td&gt;&lt;td&gt;Yes    &lt;/td&gt;&lt;td&gt;No &lt;/td&gt;&lt;td&gt;No&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Embedded on your site      &lt;/td&gt;&lt;td&gt;No    &lt;/td&gt;&lt;td&gt;Yes &lt;/td&gt;&lt;td&gt;Yes&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Customer Stays on Site   &lt;/td&gt;&lt;td&gt;No    &lt;/td&gt;&lt;td&gt;Yes &lt;/td&gt;&lt;td&gt;Yes&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;PCI Burden               &lt;/td&gt;&lt;td&gt;Lowest &lt;/td&gt;&lt;td&gt;Lowest   &lt;/td&gt;&lt;td&gt;Low&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Development Effort       &lt;/td&gt;&lt;td&gt;Low    &lt;/td&gt;&lt;td&gt;Low–Medium &lt;/td&gt;&lt;td&gt;Medium–High&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Best for Beginners       &lt;/td&gt;&lt;td&gt;Yes    &lt;/td&gt;&lt;td&gt;Yes &lt;/td&gt;&lt;td&gt;No&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Stripe manages &amp;lt;br/&amp;gt;tax/discounts/subscriptions &lt;/td&gt;&lt;td&gt;Yes &lt;/td&gt;&lt;td&gt;Yes &lt;/td&gt;&lt;td&gt;Yes (via Checkout Sessions)&amp;lt;br/&amp;gt;/ No (raw PaymentIntent)&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h3 id=&quot;next-js-16-implementation-comparison&quot;&gt;Next.js 16 Implementation Comparison&lt;/h3&gt;
&lt;p&gt;All three modes start the same way — one &lt;code&gt;checkout.sessions.create()&lt;/code&gt; call, differing only by &lt;code&gt;ui_mode&lt;/code&gt; and, for Elements, how you render the result.&lt;/p&gt;
&lt;h4 id=&quot;hosted&quot;&gt;Hosted&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-txt&quot;&gt;API Route or Server Action
  → checkout.sessions.create({ ui_mode: &quot;hosted_page&quot; })
  → redirect to session.url
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;embedded&quot;&gt;Embedded&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-txt&quot;&gt;API Route or Server Action
  → checkout.sessions.create({ ui_mode: &quot;embedded_page&quot; })
  → return session.client_secret
  → mount &amp;lt;EmbeddedCheckout&amp;gt; client-side, no redirect
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;elements&quot;&gt;Elements&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-txt&quot;&gt;API Route or Server Action
  → checkout.sessions.create({ ui_mode: &quot;elements&quot; })  (recommended)
     — or —
  → paymentIntents.create()  (manual, more control, more to maintain)
  → mount &amp;lt;PaymentElement&amp;gt; client-side
  → handle confirmation flow yourself
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The additional flexibility in Elements mode still comes with additional implementation complexity — that part hasn't changed.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;when-should-you-use-each-mode-&quot;&gt;When Should You Use Each Mode?&lt;/h3&gt;
&lt;h4 id=&quot;choose-hosted-if&quot;&gt;Choose Hosted If&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;You're new to Stripe or building an MVP&lt;/li&gt;
&lt;li&gt;You want the fastest implementation and least maintenance&lt;/li&gt;
&lt;li&gt;SaaS subscriptions, digital products, small business checkout&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;choose-embedded-if&quot;&gt;Choose Embedded If&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Your only reason to consider Elements was &quot;keep the customer on my page&quot;&lt;/li&gt;
&lt;li&gt;You want Stripe-managed tax/discounts/subscriptions but don't need a fully custom form&lt;/li&gt;
&lt;li&gt;Sidebar, modal, or chat-based checkout flows&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;choose-elements-if&quot;&gt;Choose Elements If&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Checkout UX is part of your product's differentiation&lt;/li&gt;
&lt;li&gt;You need multi-step checkouts, upsells, or deeply custom logic&lt;/li&gt;
&lt;li&gt;Enterprise or large ecommerce with requirements Stripe's prebuilt UI can't express&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;decision-matrix&quot;&gt;Decision Matrix&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Your Situation           &lt;/th&gt;&lt;th&gt;Recommendation&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;First Stripe integration  &lt;/td&gt;&lt;td&gt;Hosted&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;SaaS product               &lt;/td&gt;&lt;td&gt;Hosted&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Startup MVP                 &lt;/td&gt;&lt;td&gt;Hosted&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Want to stay on your domain, minimal custom code &lt;/td&gt;&lt;td&gt;Embedded&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Chat / modal / sidebar checkout &lt;/td&gt;&lt;td&gt;Embedded&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Custom checkout design      &lt;/td&gt;&lt;td&gt;Elements&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Enterprise application       &lt;/td&gt;&lt;td&gt;Elements&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Large ecommerce store         &lt;/td&gt;&lt;td&gt;Elements&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h3 id=&quot;performance-amp-security&quot;&gt;Performance &amp;amp; Security&lt;/h3&gt;
&lt;p&gt;Both dimensions remain effectively a tie across all three modes when implemented correctly — Stripe's infrastructure backs PCI compliance, SCA, 3D Secure, and fraud detection identically regardless of &lt;code&gt;ui_mode&lt;/code&gt;. Choose based on UX and development bandwidth, not performance or security differences.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;migration-path&quot;&gt;Migration Path&lt;/h3&gt;
&lt;p&gt;Nothing here has changed conceptually — you're still not locked in:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-txt&quot;&gt;      Launch MVP
          ↓
   Hosted (fastest)
          ↓
   Validate Product
          ↓
Need to stay on-site → Embedded
          ↓
Need full custom UX → Elements
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Since all three now share the same underlying &lt;code&gt;checkout.sessions.create()&lt;/code&gt; call, moving between hosted and embedded in particular is a smaller lift than it used to be — you're mostly changing &lt;code&gt;ui_mode&lt;/code&gt; and how you render the result, not rebuilding your integration from scratch.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;frequently-asked-questions-1&quot;&gt;Frequently Asked Questions&lt;/h3&gt;
&lt;h4 id=&quot;is-this-still-checkout-vs-payment-element-&quot;&gt;Is this still &quot;Checkout vs Payment Element&quot;?&lt;/h4&gt;
&lt;p&gt;Not officially, as of 2026 — Stripe now frames it as one Checkout Sessions API with three UI modes (hosted, embedded, elements). The old terms still describe the same UX outcomes, which is why this post keeps using them alongside the new framing.&lt;/p&gt;
&lt;h4 id=&quot;which-is-easiest-with-next-js-16-&quot;&gt;Which is easiest with Next.js 16?&lt;/h4&gt;
&lt;p&gt;Hosted, by a clear margin. Embedded is a close second and removes the &quot;customer leaves the page&quot; downside with only a modest increase in code.&lt;/p&gt;
&lt;h4 id=&quot;should-i-still-use-a-raw-paymentintent-payment-element-instead-of-checkout-sessions-elements-mode-&quot;&gt;Should I still use a raw PaymentIntent + Payment Element instead of Checkout Sessions elements mode?&lt;/h4&gt;
&lt;p&gt;Only if you're deliberately managing tax, discounts, subscriptions, and currency conversion yourself. Stripe's current docs are explicit that Checkout Sessions is the default recommendation; raw PaymentIntents are for when you want to opt out of Stripe managing those pieces.&lt;/p&gt;
&lt;h4 id=&quot;can-i-use-subscriptions-with-all-three-modes-&quot;&gt;Can I use subscriptions with all three modes?&lt;/h4&gt;
&lt;p&gt;Yes — subscription mode works the same across hosted, embedded, and elements.&lt;/p&gt;
&lt;h4 id=&quot;can-i-switch-later-&quot;&gt;Can I switch later?&lt;/h4&gt;
&lt;p&gt;Yes, and it's easier now than before since all three share the same Checkout Sessions foundation.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;final-recommendation&quot;&gt;Final Recommendation&lt;/h3&gt;
&lt;p&gt;Start with &lt;strong&gt;hosted&lt;/strong&gt; unless you have a specific reason not to — it's still the fastest path to a working, compliant checkout. Reach for &lt;strong&gt;embedded&lt;/strong&gt; the moment &quot;customer leaves my site&quot; is your only objection. Save &lt;strong&gt;elements&lt;/strong&gt; for when checkout UX is a genuine product requirement, not a default choice.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;helpful-resources&quot;&gt;Helpful Resources&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.stripe.com/api/checkout/sessions&quot;&gt;Stripe Checkout Sessions API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.stripe.com/payments/checkout&quot;&gt;Build a payments page (UI modes overview)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://stripe.com/docs/payments/payment-element&quot;&gt;Stripe Payment Element&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://nextjs.org/docs&quot;&gt;Next.js Documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;continue-learning-1&quot;&gt;Continue Learning&lt;/h3&gt;
&lt;p&gt;If you'd like step-by-step implementation guides, check out these tutorials:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://devstacked.tech/blog/stripe-nextjs-16-guide&quot;&gt;How to Integrate Stripe Checkout with Next.js 16 (2026 Edition)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://devstacked.tech/blog/stripe-payment-element-nextjs-16-guide&quot;&gt;How to Integrate Stripe Payment Element with Next.js 16 (2026 Edition)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://devstacked.tech/blog/stripe-subscription-billing-nextjs-16&quot;&gt;How to Integrate Stripe Subscriptions in Next.js 16 (2026)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://devstacked.tech/blog/usage-based-billing-with-stripe-nextjs&quot;&gt;How to Build Usage-Based Billing with Stripe (2026 Guide)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These guides walk through complete setup, API routes, server actions, webhooks, and production deployment for both Stripe integrations. Note: both linked implementation guides currently show the pre-2026 direct integration pattern (raw Checkout Session redirect / raw PaymentIntent + Elements) rather than the unified Checkout Sessions &lt;code&gt;ui_mode&lt;/code&gt; approach — updates to those are planned.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/23293/stripe-checkout-vs-payment-element-in-2026-which-one-should-you-choose</guid>
<pubDate>Thu, 23 Jul 2026 02:58:49 +0000</pubDate>
</item>
<item>
<title>How to Integrate Stripe Payment Element with Next.js 16 (2026 Edition)</title>
<link>https://coderlegion.com/22996/how-to-integrate-stripe-payment-element-with-next-js-16-2026-edition</link>
<description>&lt;p&gt;Stripe Payment Element is one of the best ways to accept payments in modern web applications. It keeps sensitive card information on Stripe servers, helping reduce PCI (Payment Card Industry) compliance complexity.&lt;/p&gt;
&lt;p&gt;In this beginner-friendly guide, you'll learn how to integrate Stripe Payment Element with Next.js 16 using:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;App Router&lt;/li&gt;
&lt;li&gt;Server Actions&lt;/li&gt;
&lt;li&gt;Payment Intents&lt;/li&gt;
&lt;li&gt;Stripe Webhooks&lt;/li&gt;
&lt;li&gt;TypeScript&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;By the end of this guide, you'll have a fully working checkout flow.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;what-we-are-building&quot;&gt;What We Are Building&lt;/h3&gt;
&lt;p&gt;In this tutorial, we'll create a simple SaaS pricing page with two packages:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Package &lt;/th&gt;&lt;th&gt;Price&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Harmony &lt;/td&gt;&lt;td&gt;399&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Legacy &lt;/td&gt;&lt;td&gt;599&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;When the user clicks a package:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A Stripe Payment Intent will be created&lt;/li&gt;
&lt;li&gt;Stripe Payment Element will appear&lt;/li&gt;
&lt;li&gt;User enters payment details&lt;/li&gt;
&lt;li&gt;Payment is confirmed&lt;/li&gt;
&lt;li&gt;Stripe webhook handles payment success&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This guide mainly focuses on the &lt;strong&gt;Stripe integration&lt;/strong&gt; rather than UI design, so we will keep the UI simple.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;two-ways-to-accept-payments-in-stripe&quot;&gt;Two Ways to Accept Payments in Stripe&lt;/h3&gt;
&lt;p&gt;Stripe provides two main checkout methods.&lt;/p&gt;
&lt;h4 id=&quot;1-stripe-hosted-checkout&quot;&gt;1. Stripe Hosted Checkout&lt;/h4&gt;
&lt;p&gt;Stripe hosts the entire checkout page.&lt;/p&gt;
&lt;p&gt;User flow:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Your Website → Stripe Checkout → Back to Your Website
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This method is easier to set up. I've full guide covering this method please visit &lt;a rel=&quot;nofollow&quot; href=&quot;https://devstacked.tech/blog/stripe-nextjs-16-guide&quot;&gt;How to Integrate Stripe Hosted Checkout with Nextjs 16&lt;/a&gt;&lt;br&gt;
.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;2-stripe-payment-element-what-we-ll-use-&quot;&gt;2. Stripe Payment Element (What We'll Use)&lt;/h4&gt;
&lt;p&gt;Stripe checkout form is embedded directly inside your website.&lt;/p&gt;
&lt;p&gt;Benefits:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Better UX&lt;/li&gt;
&lt;li&gt;Fully customizable&lt;/li&gt;
&lt;li&gt;No redirect before payment&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;create-a-next-js-16-project&quot;&gt;Create a Next.js 16 Project&lt;/h3&gt;
&lt;p&gt;Run:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx create-next-app@latest myapp
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Choose your preferred setup.&lt;/p&gt;
&lt;p&gt;Recommended:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;TypeScript&lt;/li&gt;
&lt;li&gt;App Router&lt;/li&gt;
&lt;li&gt;src directory&lt;/li&gt;
&lt;li&gt;React Compiler&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;install-stripe-packages&quot;&gt;Install Stripe Packages&lt;/h3&gt;
&lt;p&gt;Run:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm install stripe @stripe/react-stripe-js @stripe/stripe-js
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;These packages help us:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Package &lt;/th&gt;&lt;th&gt;Purpose&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;stripe &lt;/td&gt;&lt;td&gt;Server-side Stripe SDK&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;@stripe/react-stripe-js &lt;/td&gt;&lt;td&gt;React components for Stripe&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;@stripe/stripe-js &lt;/td&gt;&lt;td&gt;Loads Stripe in browser&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h3 id=&quot;stripe-setup&quot;&gt;Stripe Setup&lt;/h3&gt;
&lt;h4 id=&quot;create-stripe-server-instance&quot;&gt;Create Stripe Server Instance&lt;/h4&gt;
&lt;p&gt;Create:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;lib/stripe/server.ts
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;import Stripe from &quot;stripe&quot;;

export const stripe = new Stripe(
  process.env.STRIPE_SECRET_KEY!,
  {
    apiVersion: &quot;2026-04-22.dahlia&quot;,
  }
);
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;what-does-this-do-&quot;&gt;What Does This Do?&lt;/h4&gt;
&lt;p&gt;This creates a Stripe instance on the server side.&lt;/p&gt;
&lt;p&gt;We'll use this object to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Create Payment Intents&lt;/li&gt;
&lt;li&gt;Handle webhooks&lt;/li&gt;
&lt;li&gt;Access Stripe APIs securely&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;create-stripe-client-instance&quot;&gt;Create Stripe Client Instance&lt;/h4&gt;
&lt;p&gt;Create:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;lib/stripe/client.ts
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;import { loadStripe } from &quot;@stripe/stripe-js&quot;;

export const stripePromise = loadStripe(
  process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY!
);
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;why-do-we-need-this-&quot;&gt;Why Do We Need This?&lt;/h4&gt;
&lt;p&gt;Stripe Payment Element runs in the browser.&lt;/p&gt;
&lt;p&gt;This file loads Stripe safely on the client side.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;get-stripe-api-keys&quot;&gt;Get Stripe API Keys&lt;/h3&gt;
&lt;p&gt;Go to the &lt;a rel=&quot;nofollow&quot; href=&quot;https://dashboard.stripe.com/&quot;&gt;Stripe Dashboard&lt;/a&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Open the sidebar&lt;/li&gt;
&lt;li&gt;Enable &lt;strong&gt;Test Mode&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;https://devstacked.tech/hosted-stripe-checkout/image1.png&quot; alt=&quot;Stripe Test Mode Dashboard&quot;&gt;&lt;/p&gt;
&lt;p&gt;This gives you test keys instead of live keys.&lt;/p&gt;
&lt;p&gt;You should use:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;sk_test_&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pk_test_&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;while developing locally.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;create-environment-variables&quot;&gt;Create Environment Variables&lt;/h3&gt;
&lt;p&gt;Create:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;.env.local
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-env&quot;&gt;STRIPE_SECRET_KEY=sk_test_xxx
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_xxx
NEXT_PUBLIC_APP_URL=http://localhost:3000
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;understanding-environment-variables&quot;&gt;Understanding Environment Variables&lt;/h3&gt;
&lt;h4 id=&quot;why-nextpublic-&quot;&gt;Why NEXT&lt;em&gt;PUBLIC&lt;/em&gt;?&lt;/h4&gt;
&lt;p&gt;Variables that start with &lt;code&gt;NEXT_PUBLIC_&lt;/code&gt; can be accessed on the client side.&lt;/p&gt;
&lt;p&gt;This is safe for publishable Stripe keys.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Important:&lt;/p&gt;
&lt;p&gt;Never expose your Stripe Secret Key to the client side.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h3 id=&quot;create-package-constants&quot;&gt;Create Package Constants&lt;/h3&gt;
&lt;p&gt;Create:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;lib/constants/package-tier.ts
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;export const PKG_TIER = {
  HARMONY: &quot;harmony&quot;,
  LEGACY: &quot;legacy&quot;,
} as const;

export const PACKAGES = {
  harmony: {
    id: &quot;harmony&quot;,
    name: &quot;Harmony&quot;,
    price: 399,
  },

  legacy: {
    id: &quot;legacy&quot;,
    name: &quot;Legacy&quot;,
    price: 599,
  },
};

export type PkgTierType = keyof typeof PACKAGES;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;why-store-package-data-in-one-file-&quot;&gt;Why Store Package Data in One File?&lt;/h4&gt;
&lt;p&gt;This is a very important practice.&lt;/p&gt;
&lt;p&gt;Instead of hardcoding package names and prices everywhere in your app, we keep everything in one place.&lt;/p&gt;
&lt;p&gt;Benefits:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Easier maintenance&lt;/li&gt;
&lt;li&gt;Easier updates&lt;/li&gt;
&lt;li&gt;Less bugs&lt;/li&gt;
&lt;li&gt;Better scalability&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;p&gt;If later you rename:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Harmony → Premium
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You only update one file.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;create-checkout-form&quot;&gt;Create Checkout Form&lt;/h3&gt;
&lt;p&gt;Create:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;src/components/checkout-form.tsx
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;&quot;use client&quot;;

import {
  PaymentElement,
  useElements,
  useStripe,
} from &quot;@stripe/react-stripe-js&quot;;

import { useState, SubmitEvent } from &quot;react&quot;;

export function CheckoutForm() {
  const stripe = useStripe();
  const elements = useElements();

  const [loading, setLoading] = useState(false);
  const [error, setError] = useState&amp;lt;string | null&amp;gt;(null);

  const handleSubmit = async (e: SubmitEvent&amp;lt;HTMLFormElement&amp;gt;) =&amp;gt; {
    e.preventDefault();

    if (!stripe || !elements) return;

    setLoading(true);
    setError(null);

    const result = await stripe.confirmPayment({
      elements,
      confirmParams: {
        return_url:
          `${process.env.NEXT_PUBLIC_APP_URL}/checkout/success`,
      },
      redirect: &quot;always&quot;,
    });

    if (result.error) {
      setError(result.error.message || &quot;Payment failed&quot;);
      setLoading(false);
      return;
    }

    setLoading(false);
  };

  return (
    &amp;lt;form
      onSubmit={handleSubmit}
      className=&quot;space-y-4&quot;
    &amp;gt;
      &amp;lt;PaymentElement /&amp;gt;

      {error &amp;amp;&amp;amp; (
        &amp;lt;div className=&quot;text-red-500 text-sm&quot;&amp;gt;
          {error}
        &amp;lt;/div&amp;gt;
      )}

      &amp;lt;button
        type=&quot;submit&quot;
        disabled={!stripe || loading}
        className=&quot;w-full rounded-md bg-black px-4 py-3 text-white disabled:opacity-50&quot;
      &amp;gt;
        {loading ? &quot;Processing...&quot; : &quot;Pay Now&quot;}
      &amp;lt;/button&amp;gt;
    &amp;lt;/form&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;understanding-the-checkout-form&quot;&gt;Understanding the Checkout Form&lt;/h4&gt;
&lt;h5 id=&quot;what-is-paymentelement-&quot;&gt;What is PaymentElement?&lt;/h5&gt;
&lt;p&gt;&lt;code&gt;PaymentElement&lt;/code&gt; is Stripe's prebuilt checkout UI.&lt;/p&gt;
&lt;p&gt;It automatically supports:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Cards&lt;/li&gt;
&lt;li&gt;Apple Pay&lt;/li&gt;
&lt;li&gt;Google Pay&lt;/li&gt;
&lt;li&gt;Bank payments&lt;/li&gt;
&lt;li&gt;Many regional payment methods&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;depending on your Stripe settings.&lt;/p&gt;
&lt;p&gt;&amp;gt;Important:&lt;br&gt;
&amp;gt;Some methods may not work in development mode.&lt;/p&gt;
&lt;h5 id=&quot;what-happens-on-form-submit-&quot;&gt;What Happens on Form Submit?&lt;/h5&gt;
&lt;p&gt;When the user clicks the Pay button:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;stripe.confirmPayment()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Stripe:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Validates card details&lt;/li&gt;
&lt;li&gt;Processes payment&lt;/li&gt;
&lt;li&gt;Handles authentication&lt;/li&gt;
&lt;li&gt;Redirects user after success&lt;/li&gt;
&lt;/ol&gt;
&lt;h5 id=&quot;why-use-return-url-&quot;&gt;Why Use return_url?&lt;/h5&gt;
&lt;p&gt;After payment, Stripe redirects the user to your payment success page.&lt;/p&gt;
&lt;p&gt;Example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;/checkout/success
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;create-stripe-provider&quot;&gt;Create Stripe Provider&lt;/h3&gt;
&lt;p&gt;Create:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;src/components/stripe-provider.tsx
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;import { Elements } from &quot;@stripe/react-stripe-js&quot;;
import { stripePromise } from &quot;@/lib/stripe/client&quot;;
import { ReactNode } from &quot;react&quot;;

interface Props {
  children: ReactNode;
  clientSecret: string;
}

export function StripeProvider({
  children,
  clientSecret,
}: Props) {
  return (
    &amp;lt;Elements
      stripe={stripePromise}
      options={{
        clientSecret,
        appearance: {
          theme: &quot;night&quot;,
        },
      }}
    &amp;gt;
      {children}
    &amp;lt;/Elements&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;customizing-stripe-appearance&quot;&gt;Customizing Stripe Appearance&lt;/h4&gt;
&lt;p&gt;You can customize:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Theme&lt;/li&gt;
&lt;li&gt;Colors&lt;/li&gt;
&lt;li&gt;Fonts&lt;/li&gt;
&lt;li&gt;Border radius&lt;/li&gt;
&lt;li&gt;Input styles&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;using the &lt;code&gt;appearance&lt;/code&gt; object.&lt;/p&gt;
&lt;p&gt;For more appearance options please visit &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.stripe.com/elements/appearance-api?api-integration=paymentintents&quot;&gt;Stripe Appearance API Docs&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;create-payment-intent-server-action&quot;&gt;Create Payment Intent Server Action&lt;/h3&gt;
&lt;p&gt;Create:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;lib/actions/create-payment-intent.ts
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;&quot;use server&quot;;

export async function createPaymentIntent() {}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;What Are Server Actions?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Before creating the payment intent, let's quickly understand Server Actions.&lt;/p&gt;
&lt;p&gt;Server Actions are asynchronous functions that run on the server.&lt;/p&gt;
&lt;p&gt;They help us:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Handle forms&lt;/li&gt;
&lt;li&gt;Access databases&lt;/li&gt;
&lt;li&gt;Call APIs securely&lt;/li&gt;
&lt;li&gt;Mutate data&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;without creating traditional API routes.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;create-package-list-component&quot;&gt;Create Package List Component&lt;/h3&gt;
&lt;p&gt;Create:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;src/components/package-list.tsx
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;&quot;use client&quot;;

import { useActionState } from &quot;react&quot;;

import { CheckoutForm } from &quot;@/components/checkout-form&quot;;
import { StripeProvider } from &quot;@/components/stripe-provider&quot;;

import { createPaymentIntent } from &quot;@/lib/actions/create-payment-intent&quot;;

import { PACKAGES } from &quot;@/lib/constants/package-tier&quot;;

export default function PackageList() {
  const [state, action, isPending] = useActionState(createPaymentIntent, { clientSecret: &quot;&quot;, error: &quot;&quot; });

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;form action={action}&amp;gt;
        {Object.values(PACKAGES).map(
          (pkg) =&amp;gt; (
            &amp;lt;button
              key={pkg.id}
              type=&quot;submit&quot;
              name=&quot;packageTier&quot;
              value={pkg.id}
              disabled={
                isPending ||
                !!state?.clientSecret
              }
            &amp;gt;
              {pkg.name}
            &amp;lt;/button&amp;gt;
          )
        )}
      &amp;lt;/form&amp;gt;

      {state?.error &amp;amp;&amp;amp; (
        &amp;lt;div&amp;gt;{state.error}&amp;lt;/div&amp;gt;
      )}

      {state?.clientSecret &amp;amp;&amp;amp; (
        &amp;lt;StripeProvider
          clientSecret={
            state.clientSecret
          }
        &amp;gt;
          &amp;lt;CheckoutForm /&amp;gt;
        &amp;lt;/StripeProvider&amp;gt;
      )}
    &amp;lt;/div&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;gt;&lt;strong&gt;Important&lt;/strong&gt;: The name attribute is very important inside form on button element. Later we’ll access value from form data in our server action using this name attribute. &lt;/p&gt;
&lt;h4 id=&quot;understanding-useactionstate&quot;&gt;Understanding useActionState&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;useActionState()&lt;/code&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;connects forms with Server Actions&lt;/li&gt;
&lt;li&gt;manages loading state&lt;/li&gt;
&lt;li&gt;handles server responses&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It accepts:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;your action function&lt;/li&gt;
&lt;li&gt;initial state&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;And returns:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;current state&lt;/li&gt;
&lt;li&gt;form action&lt;/li&gt;
&lt;li&gt;pending state&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;why-use-use-client-&quot;&gt;Why Use &quot;use client&quot;?&lt;/h4&gt;
&lt;p&gt;Hooks only work in client components.&lt;/p&gt;
&lt;p&gt;Since we're using:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;useActionState()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;we must add:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;&quot;use client&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;add-component-to-homepage&quot;&gt;Add Component to Homepage&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;import PackageList from &quot;@/components/package-list&quot;;

export default function Page() {
  return &amp;lt;PackageList /&amp;gt;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;update-server-action&quot;&gt;Update Server Action&lt;/h3&gt;
&lt;p&gt;Now let's add the actual logic.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;&quot;use server&quot;;

import { stripe } from &quot;@/lib/stripe/server&quot;;

import {
  PACKAGES,
  PkgTierType,
} from &quot;../constants/package-tier&quot;;

export async function createPaymentIntent(
  prev: unknown,
  formData: FormData
): Promise&amp;lt;{
  clientSecret?: string;
  error?: string;
}&amp;gt; {
  try {
    const pkgTier =
      formData.get(&quot;packageTier&quot;);

    if (
      typeof pkgTier !== &quot;string&quot; ||
      !(pkgTier in PACKAGES)
    ) {
      return {
        error: &quot;Invalid package tier&quot;,
      };
    }

    const pkg =
      PACKAGES[pkgTier as PkgTierType];

    const paymentIntent =
      await stripe.paymentIntents.create({
        amount: pkg.price * 100,

        currency: &quot;usd&quot;,

        automatic_payment_methods: {
          enabled: true,
        },

        metadata: {
          packageTier: pkgTier,
        },
      });

    if (!paymentIntent.client_secret) {
      return {
        error:
          &quot;Failed to create payment intent&quot;,
      };
    }

    return {
      clientSecret:
        paymentIntent.client_secret,
    };
  } catch (err) {
    return {
      error:
        err instanceof Error
          ? err.message
          : &quot;Something went wrong&quot;,
    };
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now let’s understand this action:&lt;/p&gt;
&lt;p&gt;Firstly it has two parameters prev we’re not using this, our default state this paramater is provided by useActionState and second formData this paramater is provided by form element.&lt;/p&gt;
&lt;p&gt;Now we’ll use that same name attribute &lt;code&gt;packageTier&lt;/code&gt; we provided inside form element to get the package that user has clicked on. After validation we’ll create stripe payment intent now let’s understand stripe object.&lt;/p&gt;
&lt;h4 id=&quot;understanding-payment-intents&quot;&gt;Understanding Payment Intents&lt;/h4&gt;
&lt;p&gt;A Payment Intent represents a payment session in Stripe.&lt;/p&gt;
&lt;p&gt;It tracks:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Payment amount&lt;/li&gt;
&lt;li&gt;Currency&lt;/li&gt;
&lt;li&gt;Payment status&lt;/li&gt;
&lt;li&gt;Payment method&lt;/li&gt;
&lt;li&gt;Authentication steps&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;why-multiply-amount-by-100-&quot;&gt;Why Multiply Amount by 100?&lt;/h4&gt;
&lt;p&gt;Stripe expects amounts in cents.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Price &lt;/th&gt;&lt;th&gt;Stripe Amount&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;$10 &lt;/td&gt;&lt;td&gt;1000&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;$399 &lt;/td&gt;&lt;td&gt;39900&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h4 id=&quot;what-is-metadata-&quot;&gt;What is Metadata?&lt;/h4&gt;
&lt;p&gt;Metadata allows us to attach extra information to payments.&lt;/p&gt;
&lt;p&gt;Example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;metadata: {
  packageTier: pkgTier
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This becomes very useful later inside webhooks.&lt;/p&gt;
&lt;h4 id=&quot;what-does-automatic-payment-methods-do-&quot;&gt;What Does automatic_payment_methods Do?&lt;/h4&gt;
&lt;p&gt;When enabled:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;automatic_payment_methods: {
  enabled: true
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Stripe automatically shows compatible payment methods.&lt;/p&gt;
&lt;p&gt;This is the easiest and recommended approach.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;stripe-webhooks&quot;&gt;Stripe Webhooks&lt;/h3&gt;
&lt;p&gt;Now the checkout works.&lt;br&gt;
But we still need a secure way to confirm payments.&lt;br&gt;
This is where webhooks come in.  &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What Are Stripe Webhooks?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Stripe webhooks are server endpoints that Stripe calls automatically whenever events happen.&lt;/p&gt;
&lt;p&gt;Examples:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Payment succeeded&lt;/li&gt;
&lt;li&gt;Payment failed&lt;/li&gt;
&lt;li&gt;Subscription renewed&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Webhooks are extremely important because:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Never trust frontend payment success alone.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Always verify payments on the server.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;create-webhook-route&quot;&gt;Create Webhook Route&lt;/h3&gt;
&lt;p&gt;Create:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;src/app/api/webhook/route.ts
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;configure-webhook-in-stripe-dashboard&quot;&gt;Configure Webhook in Stripe Dashboard&lt;/h3&gt;
&lt;p&gt;From Stripe Dashboard:&lt;/p&gt;
&lt;p&gt;Developers → Webhooks → Add Destination&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://devstacked.tech/hosted-stripe-checkout/image4.png&quot; alt=&quot;Stripe Dashboard Developers Menu&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Select Events&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Choose:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;payment_intent.succeeded&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;payment_intent.payment_failed&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Add Webhook URL&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;https://yourdomain.com/api/webhook
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;localhost-webhook-problem&quot;&gt;Localhost Webhook Problem&lt;/h3&gt;
&lt;p&gt;Stripe cannot access localhost directly.&lt;/p&gt;
&lt;p&gt;For local development, use:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ngrok&lt;/li&gt;
&lt;li&gt;Stripe CLI&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;to expose your localhost server publicly. For more info please check:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.stripe.com/stripe-cli&quot;&gt;Stripe CLI Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://ngrok.com/docs/start&quot;&gt;Ngrok Docs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;add-webhook-secret&quot;&gt;Add Webhook Secret&lt;/h3&gt;
&lt;p&gt;Copy the signing secret.&lt;/p&gt;
&lt;p&gt;Add to &lt;code&gt;.env.local&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-env&quot;&gt;STRIPE_WEBHOOK_SECRET=whsec_xxx
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;create-webhook-route-logic&quot;&gt;Create Webhook Route Logic&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;export async function POST(req: Request) {
    try {
        const payload = await req.text();
        const sig = (await headers()).get('stripe-signature')!;
        let event;
        try {
            event = stripe.webhooks.constructEvent(payload, sig, endpointSecret);
        } catch (err) {
            const errorMessage = err instanceof Error ? err.message : 'Unknown error';
            return NextResponse.json(
                { message: `Webhook Error: ${errorMessage}` },
                { status: 400 }
            );
        }
        switch (event.type) {
            case &quot;payment_intent.succeeded&quot;: {
                const paymentIntent = event.data.object as Stripe.PaymentIntent;

                if (paymentIntent.status !== 'succeeded') {
                    return NextResponse.json(
                        { message: 'Payment not completed' },
                        { status: 200 }
                    );
                }

                const pkgTier = paymentIntent.metadata?.packageTier;
                console.log(&quot;pkgTier&quot;, pkgTier);
                console.log(&quot;Payment success&quot;, paymentIntent.id);

                // Update database
                // Mark order paid
                // Send email
                // Trigger fulfillment

                break;
            }

            case &quot;payment_intent.payment_failed&quot;: {
                console.log(&quot;Payment failed&quot;);
                break;
            }
        }

        return NextResponse.json({ message: 'Received' }, { status: 200 });

    } catch (err) {
        const error = err instanceof Error ? err.message : 'Something went wrong';
        console.error('error', error);

        return NextResponse.json(
            { error },
            { status: 500 }
        );
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;understanding-the-webhook&quot;&gt;Understanding the Webhook&lt;/h3&gt;
&lt;h4 id=&quot;why-use-req-text-&quot;&gt;Why Use req.text()?&lt;/h4&gt;
&lt;p&gt;Stripe requires the raw request body for signature verification.&lt;/p&gt;
&lt;p&gt;In App Router:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;await req.text()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;works correctly without additional body parser configuration.&lt;/p&gt;
&lt;h4 id=&quot;what-does-constructevent-do-&quot;&gt;What Does constructEvent Do?&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;stripe.webhooks.constructEvent()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This verifies that the webhook request actually came from Stripe.&lt;/p&gt;
&lt;p&gt;Without this verification, anyone could fake webhook requests.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;important-production-tip&quot;&gt;Important Production Tip&lt;/h4&gt;
&lt;p&gt;Stripe may send the same webhook event multiple times.&lt;/p&gt;
&lt;p&gt;Always:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Store Stripe event IDs&lt;/li&gt;
&lt;li&gt;Ignore duplicate events&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This prevents duplicate orders or emails.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;why-webhooks-matter&quot;&gt;Why Webhooks Matter&lt;/h4&gt;
&lt;p&gt;Your webhook is the safest place to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Mark orders as paid&lt;/li&gt;
&lt;li&gt;Save payment records&lt;/li&gt;
&lt;li&gt;Send confirmation emails&lt;/li&gt;
&lt;li&gt;Start product fulfillment&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Never trust only frontend success pages.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;why-stripe-expects-200&quot;&gt;Why Stripe expects 200&lt;/h4&gt;
&lt;p&gt;Notice in each return statement status is mostly 200.&lt;br&gt;
When Stripe sends a webhook event, it waits for your server response.&lt;/p&gt;
&lt;p&gt;200–299 → Stripe marks event as delivered successfully&lt;br&gt;
400–499 → Stripe assumes your request is invalid&lt;br&gt;
500+ → Stripe assumes your server failed&lt;/p&gt;
&lt;p&gt;If Stripe does NOT get a 2xx response, it retries the webhook multiple times automatically.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;testing-stripe-payments&quot;&gt;Testing Stripe Payments&lt;/h3&gt;
&lt;p&gt;Use these test cards.&lt;/p&gt;
&lt;h4 id=&quot;successful-payment&quot;&gt;Successful Payment&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;4242 4242 4242 4242
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;declined-card&quot;&gt;Declined Card&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;4000 0000 0000 0002
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;3d-secure&quot;&gt;3D Secure&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;4000 0025 0000 3155
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;production-best-practices&quot;&gt;Production Best Practices&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Best Practice &lt;/th&gt;&lt;th&gt;Why It Matters&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Use Webhooks &lt;/td&gt;&lt;td&gt;Never trust client-side payment success&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Validate Metadata &lt;/td&gt;&lt;td&gt;Prevent invalid purchases&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Store Event IDs &lt;/td&gt;&lt;td&gt;Avoid duplicate processing&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Use HTTPS &lt;/td&gt;&lt;td&gt;Required for secure payments&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Keep Secret Keys Server Side &lt;/td&gt;&lt;td&gt;Prevent security leaks&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h3 id=&quot;final-thoughts&quot;&gt;Final Thoughts&lt;/h3&gt;
&lt;p&gt;Stripe Payment Element is one of the best ways to build modern checkout flows according to your website theme in Next.js applications.&lt;/p&gt;
&lt;p&gt;You now know how to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Create Payment Intents&lt;/li&gt;
&lt;li&gt;Use Stripe Payment Element&lt;/li&gt;
&lt;li&gt;Handle payments&lt;/li&gt;
&lt;li&gt;Use Server Actions&lt;/li&gt;
&lt;li&gt;Verify Stripe webhooks&lt;/li&gt;
&lt;li&gt;Build a production-ready checkout flow&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This setup works great for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;SaaS products&lt;/li&gt;
&lt;li&gt;Digital products&lt;/li&gt;
&lt;li&gt;Agencies&lt;/li&gt;
&lt;li&gt;AI tools&lt;/li&gt;
&lt;li&gt;Membership websites&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I hope this guide helps you build your own Stripe integration successfully.&lt;/p&gt;
&lt;h4 id=&quot;useful-resources&quot;&gt;Useful Resources&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;React useActionState Docs: &lt;a rel=&quot;nofollow&quot; href=&quot;https://react.dev/reference/react/useActionState&quot;&gt;https://react.dev/reference/react/useActionState&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Next.js Server Actions Docs: &lt;a rel=&quot;nofollow&quot; href=&quot;https://nextjs.org/docs/app/getting-started/mutating-data&quot;&gt;https://nextjs.org/docs/app/getting-started/mutating-data&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Stripe Payment Element Docs: &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.stripe.com/payments/payment-element&quot;&gt;https://docs.stripe.com/payments/payment-element&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;continue-learning-2&quot;&gt;Continue Learning&lt;/h4&gt;
&lt;p&gt;If you'd like to learn more about stripe, check out these tutorials:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://devstacked.tech/blog/stripe-nextjs-16-guide&quot;&gt;How to Integrate Stripe Checkout with Next.js 16 (2026 Edition)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://devstacked.tech/blog/stripe-checkout-vs-payment-element&quot;&gt;Stripe Checkout vs Payment Element in 2026: Which One Should You Choose?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://devstacked.tech/blog/stripe-checkout-vs-payment-element&quot;&gt;Stripe Subscriptions in Next.js 16: Complete SaaS Billing Guide (2026)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/22996/how-to-integrate-stripe-payment-element-with-next-js-16-2026-edition</guid>
<pubDate>Sun, 19 Jul 2026 05:52:33 +0000</pubDate>
</item>
<item>
<title>How to Integrate Stripe Checkout with Next.js 16 (2026 Edition)</title>
<link>https://coderlegion.com/22867/how-to-integrate-stripe-checkout-with-next-js-16-2026-edition</link>
<description>&lt;p&gt;If you're building a SaaS product with Next.js, one of the fastest and most reliable ways to accept payments is by using Stripe Checkout.&lt;/p&gt;
&lt;p&gt;In this guide, you'll learn how to integrate Stripe Checkout with Next.js 16 using:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;App Router&lt;/li&gt;
&lt;li&gt;Server Actions&lt;/li&gt;
&lt;li&gt;TypeScript&lt;/li&gt;
&lt;li&gt;Stripe Webhooks&lt;/li&gt;
&lt;li&gt;Hosted Checkout Pages&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;By the end, you'll have a fully working Stripe payment flow with webhook handling and order verification.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;what-we-re-building&quot;&gt;What We’re Building&lt;/h3&gt;
&lt;p&gt;In this tutorial, we’ll build a simple SaaS pricing flow where users can purchase one of two packages:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Harmony&lt;/li&gt;
&lt;li&gt;Legacy&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When a user clicks a checkout button:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A Stripe Checkout Session will be created&lt;/li&gt;
&lt;li&gt;The user will be redirected to Stripe’s hosted checkout page&lt;/li&gt;
&lt;li&gt;After payment, Stripe redirects the user back to your website&lt;/li&gt;
&lt;li&gt;Stripe sends a webhook event to your Next.js application&lt;/li&gt;
&lt;li&gt;Your app verifies the payment and creates an order&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This guide focuses mainly on Stripe + Next.js integration rather than UI design.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;two-ways-to-integrate-stripe-checkout&quot;&gt;Two Ways to Integrate Stripe Checkout&lt;/h3&gt;
&lt;h4 id=&quot;1-hosted-stripe-checkout&quot;&gt;1. Hosted Stripe Checkout&lt;/h4&gt;
&lt;p&gt;In this method:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Users are redirected to a secure checkout page hosted by Stripe&lt;/li&gt;
&lt;li&gt;Stripe handles payment security and PCI compliance&lt;/li&gt;
&lt;li&gt;After payment, users are redirected back to your website&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is the easiest and safest option for most SaaS applications.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;2-embedded-stripe-checkout&quot;&gt;2. Embedded Stripe Checkout&lt;/h4&gt;
&lt;p&gt;In this approach:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Stripe Checkout is embedded directly inside your website&lt;/li&gt;
&lt;li&gt;You can customize the UI more deeply&lt;/li&gt;
&lt;li&gt;No external redirect is required&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This guide focuses on the hosted checkout approach but I've full guide covering this second method please visit &lt;a rel=&quot;nofollow&quot; href=&quot;https://devstacked.tech/blog/stripe-payment-element-nextjs-16-guide#useful-resources&quot;&gt;How to Integrate Stripe Payment Element with Nextjs 16&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;create-a-next-js-16-project-1&quot;&gt;Create a Next.js 16 Project&lt;/h3&gt;
&lt;p&gt;Create a new Next.js application:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx create-next-app@latest myapp
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Choose the options that fit your setup.&lt;/p&gt;
&lt;p&gt;Recommended:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;TypeScript&lt;/li&gt;
&lt;li&gt;App Router&lt;/li&gt;
&lt;li&gt;&lt;code&gt;src/&lt;/code&gt; directory&lt;/li&gt;
&lt;li&gt;React Compiler&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;install-stripe&quot;&gt;Install Stripe&lt;/h3&gt;
&lt;p&gt;Install the official Stripe SDK:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm install stripe
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;configure-stripe-server-sdk&quot;&gt;Configure Stripe Server SDK&lt;/h3&gt;
&lt;p&gt;Create the following file:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-txt&quot;&gt;src/lib/stripe/server.ts
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Add this code:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;import Stripe from 'stripe';

export const stripe = new Stripe(
  process.env.STRIPE_SECRET_KEY!,
  {
    apiVersion: '2026-04-22.dahlia'
  }
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This initializes the Stripe SDK using your secret API key.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;get-your-stripe-api-keys&quot;&gt;Get Your Stripe API Keys&lt;/h3&gt;
&lt;p&gt;Go to:&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://dashboard.stripe.com&quot;&gt;https://dashboard.stripe.com&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Enable &lt;strong&gt;Test Mode&lt;/strong&gt; from the dashboard.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://devstacked.tech/hosted-stripe-checkout/image1.png&quot; alt=&quot;Stripe Test Mode Dashboard&quot;&gt;&lt;/p&gt;
&lt;p&gt;Copy your:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;sk_test_...&lt;/code&gt; secret key&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pk_test_...&lt;/code&gt; publishable key&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;configure-environment-variables&quot;&gt;Configure Environment Variables&lt;/h3&gt;
&lt;p&gt;Create a &lt;code&gt;.env.local&lt;/code&gt; file in your project root:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-env&quot;&gt;STRIPE_SECRET_KEY=sk_test_****
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_****
NEXT_PUBLIC_APP_URL=http://localhost:3000
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;important-notes&quot;&gt;Important Notes&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;STRIPE_SECRET_KEY&lt;/code&gt; should only be used on the server&lt;/li&gt;
&lt;li&gt;&lt;code&gt;NEXT_PUBLIC_&lt;/code&gt; variables are exposed to the client&lt;/li&gt;
&lt;li&gt;Always switch to live keys before production deployment&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;create-products-in-stripe&quot;&gt;Create Products in Stripe&lt;/h3&gt;
&lt;p&gt;Inside the Stripe dashboard:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Go to &lt;strong&gt;Product Catalog&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Create Product&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Enter:&lt;ul&gt;
&lt;li&gt;Product name&lt;/li&gt;
&lt;li&gt;Pricing&lt;/li&gt;
&lt;li&gt;Currency&lt;/li&gt;
&lt;li&gt;Billing type&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If users pay once:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Select &lt;strong&gt;One-off&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If users subscribe monthly/yearly:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Select &lt;strong&gt;Recurring&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://devstacked.tech/hosted-stripe-checkout/image2.png&quot; alt=&quot;Stripe Add Product Form&quot;&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;copy-stripe-price-ids&quot;&gt;Copy Stripe Price IDs&lt;/h3&gt;
&lt;p&gt;After creating products:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Open the product&lt;/li&gt;
&lt;li&gt;Click the three-dot menu&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Copy Price ID&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;https://devstacked.tech/hosted-stripe-checkout/image3.png&quot; alt=&quot;Stripe Product Details Page&quot;&gt;&lt;/p&gt;
&lt;p&gt;Add them to &lt;code&gt;.env.local&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-env&quot;&gt;NEXT_PUBLIC_STRIPE_HARMONY_PRICE_ID=price_xxx
NEXT_PUBLIC_STRIPE_LEGACY_PRICE_ID=price_xxx
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;create-package-constants-1&quot;&gt;Create Package Constants&lt;/h3&gt;
&lt;p&gt;Create:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-txt&quot;&gt;src/lib/constants/package-tier.ts
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Add:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;export const PKG_TIER = {
  HARMONY: 'harmony',
  LEGACY: 'legacy'
} as const;

export const PACKAGES = {
  harmony: {
    id: 'harmony',
    name: 'Harmony',
    description: 'The complete memorial song',
    price: 399,
    turnaroundHours: 72,
    maxRevisions: 2,
    stripePriceId: process.env.NEXT_PUBLIC_STRIPE_HARMONY_PRICE_ID!,
    features: [
      'Custom AI-generated memorial song',
      'Two rounds of lyric revisions',
      'Single-photo video with scrolling lyrics',
      'Social media versions'
    ]
  },

  legacy: {
    id: 'legacy',
    name: 'Legacy',
    description: 'Their full story, beautifully told',
    price: 599,
    turnaroundHours: 96,
    maxRevisions: 3,
    stripePriceId: process.env.NEXT_PUBLIC_STRIPE_LEGACY_PRICE_ID!,
    features: [
      'Everything in Harmony',
      'Multi-photo slideshow video',
      'Video revisions',
      'Social media versions'
    ]
  }
};

export type PkgTierType = keyof typeof PACKAGES;
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;why-use-constants-instead-of-hardcoded-strings-&quot;&gt;Why Use Constants Instead of Hardcoded Strings?&lt;/h3&gt;
&lt;p&gt;Avoid writing values like &lt;code&gt;&quot;harmony&quot;&lt;/code&gt; everywhere in your app.&lt;/p&gt;
&lt;p&gt;Using constants:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;reduces bugs&lt;/li&gt;
&lt;li&gt;improves autocomplete&lt;/li&gt;
&lt;li&gt;makes refactoring easier&lt;/li&gt;
&lt;li&gt;keeps your app scalable&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;create-a-server-action&quot;&gt;Create a Server Action&lt;/h3&gt;
&lt;p&gt;Create:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-txt&quot;&gt;src/lib/actions/start-checkout.ts
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Add:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;&quot;use server&quot;;

export async function startCheckout() {

}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;what-are-server-actions-&quot;&gt;What Are Server Actions?&lt;/h3&gt;
&lt;p&gt;Server Actions are asynchronous functions executed on the server in Next.js.&lt;/p&gt;
&lt;p&gt;They are commonly used for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;form submissions&lt;/li&gt;
&lt;li&gt;database mutations&lt;/li&gt;
&lt;li&gt;payment processing&lt;/li&gt;
&lt;li&gt;secure backend logic&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Official docs:&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations&quot;&gt;https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;create-package-ui&quot;&gt;Create Package UI&lt;/h3&gt;
&lt;p&gt;Create:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-txt&quot;&gt;src/components/package-list.tsx
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Add:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;'use client';

import { useActionState } from &quot;react&quot;;
import { startCheckout } from &quot;@/lib/actions/start-checkout&quot;;
import { PACKAGES } from &quot;@/lib/constants/package-tier&quot;;

export default function PackageList() {
  const [state, action, isPending] = useActionState(
    startCheckout,
    { error: '' }
  );

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;form action={action}&amp;gt;
        {Object.values(PACKAGES).map(pkg =&amp;gt; (
          &amp;lt;button
            key={pkg.id}
            type=&quot;submit&quot;
            name=&quot;packageTier&quot;
            value={pkg.id}
            disabled={isPending}
            className=&quot;border border-gray-300 p-3 disabled:opacity-50&quot;
          &amp;gt;
            {isPending ? 'Redirecting...' : pkg.name}
          &amp;lt;/button&amp;gt;
        ))}
      &amp;lt;/form&amp;gt;

      {state?.error &amp;amp;&amp;amp; (
        &amp;lt;div className=&quot;text-red-500&quot;&amp;gt;
          {state.error}
        &amp;lt;/div&amp;gt;
      )}
    &amp;lt;/div&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;understanding-useactionstate-1&quot;&gt;Understanding useActionState&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;useActionState()&lt;/code&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;connects forms with Server Actions&lt;/li&gt;
&lt;li&gt;manages loading state&lt;/li&gt;
&lt;li&gt;handles server responses&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It accepts:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;your action function&lt;/li&gt;
&lt;li&gt;initial state&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;And returns:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;current state&lt;/li&gt;
&lt;li&gt;form action&lt;/li&gt;
&lt;li&gt;pending state&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;render-packages-on-home-page&quot;&gt;Render Packages on Home Page&lt;/h3&gt;
&lt;p&gt;Update your homepage:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;import PackageList from &quot;@/components/package-list&quot;;

export default function Page() {
  return &amp;lt;PackageList /&amp;gt;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;create-stripe-checkout-session&quot;&gt;Create Stripe Checkout Session&lt;/h3&gt;
&lt;p&gt;Now update:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-txt&quot;&gt;src/lib/actions/start-checkout.ts
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;&quot;use server&quot;;

import { redirect } from &quot;next/navigation&quot;;
import { PACKAGES, PkgTierType } from &quot;../constants/package-tier&quot;;
import { stripe } from &quot;../stripe/server&quot;;

export async function startCheckout(
  prev: unknown,
  formData: FormData
) {
  try {
    const pkgTier = formData.get('packageTier') as PkgTierType;

    if (!pkgTier || !PACKAGES[pkgTier]) {
      return { error: 'Invalid package tier' };
    }

    const session = await stripe.checkout.sessions.create({
      mode: 'payment',

      line_items: [
        {
          price: PACKAGES[pkgTier].stripePriceId,
          quantity: 1
        }
      ],

      success_url:
        `${process.env.NEXT_PUBLIC_APP_URL}/checkout/success?session_id={CHECKOUT_SESSION_ID}`,

      cancel_url:
        `${process.env.NEXT_PUBLIC_APP_URL}/`,

      metadata: {
        packageTier: pkgTier
      },

      customer_creation: 'always',

      billing_address_collection: 'auto',

      phone_number_collection: {
        enabled: true
      }
    });

    if (!session.url) {
      throw new Error('Failed to create checkout session');
    }

    redirect(session.url);

  } catch (err) {
    return {
      error:
        err instanceof Error
          ? err.message
          : 'Failed to checkout'
    };
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;understanding-the-checkout-session&quot;&gt;Understanding the Checkout Session&lt;/h3&gt;
&lt;h4 id=&quot;mode&quot;&gt;mode&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;mode: 'payment'
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Used for one-time payments.&lt;/p&gt;
&lt;p&gt;For subscriptions use:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;mode: 'subscription'
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h4 id=&quot;line-items&quot;&gt;line_items&lt;/h4&gt;
&lt;p&gt;Defines what the user is purchasing.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;success-url&quot;&gt;success_url&lt;/h4&gt;
&lt;p&gt;Where Stripe redirects users after successful payment.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;cancel-url&quot;&gt;cancel_url&lt;/h4&gt;
&lt;p&gt;Where Stripe redirects users after cancellation.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;metadata&quot;&gt;metadata&lt;/h4&gt;
&lt;p&gt;Useful for attaching:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;package IDs&lt;/li&gt;
&lt;li&gt;user IDs&lt;/li&gt;
&lt;li&gt;internal references&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h4 id=&quot;customer-creation&quot;&gt;customer_creation&lt;/h4&gt;
&lt;p&gt;Automatically creates Stripe customers.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;important-security-note&quot;&gt;Important Security Note&lt;/h3&gt;
&lt;p&gt;Never trust prices coming from the frontend.&lt;/p&gt;
&lt;p&gt;Always:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;validate product IDs&lt;/li&gt;
&lt;li&gt;validate Stripe price IDs&lt;/li&gt;
&lt;li&gt;verify payments server-side&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;create-stripe-webhook-route&quot;&gt;Create Stripe Webhook Route&lt;/h3&gt;
&lt;p&gt;Create:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-txt&quot;&gt;src/app/api/webhook/route.ts
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;configure-stripe-webhooks&quot;&gt;Configure Stripe Webhooks&lt;/h3&gt;
&lt;p&gt;Inside Stripe dashboard:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Go to &lt;strong&gt;Developers&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Webhooks&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;https://devstacked.tech/hosted-stripe-checkout/image4.png&quot; alt=&quot;Stripe Dashboard Developers Menu&quot;&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Click &lt;strong&gt;Add Destination&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Enter:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-txt&quot;&gt;https://yourdomain.com/api/webhook
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Select:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;checkout.session.completed&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Copy the signing secret&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Add to &lt;code&gt;.env.local&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-env&quot;&gt;STRIPE_WEBHOOK_SECRET=whsec_xxx
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;test-webhooks-locally&quot;&gt;Test Webhooks Locally&lt;/h3&gt;
&lt;p&gt;Install the Stripe CLI:&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.stripe.com/stripe-cli&quot;&gt;https://docs.stripe.com/stripe-cli&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Run:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;stripe listen --forward-to localhost:3000/api/webhook
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Stripe will generate a webhook signing secret for local development.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;implement-the-webhook-handler&quot;&gt;Implement the Webhook Handler&lt;/h3&gt;
&lt;p&gt;Add:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;import { PACKAGES } from &quot;@/lib/constants/package-tier&quot;;
import { stripe } from &quot;@/lib/stripe/server&quot;;
import { headers } from &quot;next/headers&quot;;
import { NextResponse } from &quot;next/server&quot;;
import Stripe from &quot;stripe&quot;;

const endpointSecret =
  process.env.STRIPE_WEBHOOK_SECRET!;

export async function POST(req: Request) {
  try {
    const payload = await req.text();

    const sig =
      (await headers()).get('stripe-signature')!;

    let event: Stripe.Event;

    try {
      event = stripe.webhooks.constructEvent(
        payload,
        sig,
        endpointSecret
      );

    } catch (err) {
      const errorMessage =
        err instanceof Error
          ? err.message
          : 'Unknown error';

      return NextResponse.json(
        { message: `Webhook Error: ${errorMessage}` },
        { status: 400 }
      );
    }

    if (event.type === 'checkout.session.completed') {

      const session =
        event.data.object as Stripe.Checkout.Session;

      if (session.payment_status !== 'paid') {
        return NextResponse.json(
          { message: 'Payment not completed' },
          { status: 200 }
        );
      }

      const pkgTier =
        session.metadata?.packageTier;

      const customerDetails =
        session.customer_details;

      const customerEmail =
        customerDetails?.email;

      if (!pkgTier || !(pkgTier in PACKAGES)) {
        throw new Error('Invalid package tier');
      }

      if (!customerEmail) {
        throw new Error('Customer email required');
      }

      // Create database order here
      // Send confirmation email here
    }

    return NextResponse.json(
      { message: 'Received' },
      { status: 200 }
    );

  } catch (err) {

    const error =
      err instanceof Error
        ? err.message
        : 'Something went wrong';

    console.error(error);

    return NextResponse.json(
      { error },
      { status: 500 }
    );
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;why-use-req-text--1&quot;&gt;Why Use &lt;code&gt;req.text()&lt;/code&gt;?&lt;/h3&gt;
&lt;p&gt;In Next.js App Router route handlers, we can safely use:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;await req.text()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Unlike Pages Router, no additional body-parser configuration is required.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;prevent-duplicate-orders&quot;&gt;Prevent Duplicate Orders&lt;/h3&gt;
&lt;p&gt;Stripe automatically retries failed webhooks.&lt;/p&gt;
&lt;p&gt;Always store:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Stripe event IDs&lt;/li&gt;
&lt;li&gt;checkout session IDs&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;before processing orders.&lt;/p&gt;
&lt;p&gt;This prevents:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;duplicate emails&lt;/li&gt;
&lt;li&gt;duplicate orders&lt;/li&gt;
&lt;li&gt;duplicate fulfillment&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;subscription-webhook-events&quot;&gt;Subscription Webhook Events&lt;/h3&gt;
&lt;p&gt;This guide focuses on one-time payments.&lt;/p&gt;
&lt;p&gt;If you're using subscriptions, also listen for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;invoice.paid&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;customer.subscription.updated&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;customer.subscription.deleted&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;test-your-checkout&quot;&gt;Test Your Checkout&lt;/h3&gt;
&lt;p&gt;Start your Next.js app:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm run dev
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Click one of your package buttons.&lt;/p&gt;
&lt;p&gt;You should:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Redirect to Stripe Checkout&lt;/li&gt;
&lt;li&gt;Complete payment&lt;/li&gt;
&lt;li&gt;Return to your website&lt;/li&gt;
&lt;li&gt;Receive webhook events successfully&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h3 id=&quot;final-thoughts-1&quot;&gt;Final Thoughts&lt;/h3&gt;
&lt;p&gt;You now have a fully working Stripe Checkout integration using:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Next.js 16&lt;/li&gt;
&lt;li&gt;App Router&lt;/li&gt;
&lt;li&gt;Server Actions&lt;/li&gt;
&lt;li&gt;Stripe Webhooks&lt;/li&gt;
&lt;li&gt;TypeScript&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This setup is scalable, production-friendly, and ideal for SaaS applications.&lt;/p&gt;
&lt;p&gt;From here you can expand into:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;subscriptions&lt;/li&gt;
&lt;li&gt;invoices&lt;/li&gt;
&lt;li&gt;customer portals&lt;/li&gt;
&lt;li&gt;usage billing&lt;/li&gt;
&lt;li&gt;Stripe Connect&lt;/li&gt;
&lt;li&gt;embedded checkout&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;useful-resources-1&quot;&gt;Useful Resources&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Stripe Documentation: &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.stripe.com&quot;&gt;https://docs.stripe.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Next.js Documentation: &lt;a rel=&quot;nofollow&quot; href=&quot;https://nextjs.org/docs&quot;&gt;https://nextjs.org/docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Stripe Checkout Docs: &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.stripe.com/payments/checkout&quot;&gt;https://docs.stripe.com/payments/checkout&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;continue-learning-3&quot;&gt;Continue Learning&lt;/h3&gt;
&lt;p&gt;If you'd like to learn more about stripe, check out these tutorials:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://devstacked.tech/blog/stripe-payment-element-nextjs-16-guide&quot;&gt;How to Integrate Stripe Payment Element with Next.js 16 (2026 Edition)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://devstacked.tech/blog/stripe-checkout-vs-payment-element&quot;&gt;Stripe Checkout vs Payment Element in 2026: Which One Should You Choose?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://devstacked.tech/blog/stripe-checkout-vs-payment-element&quot;&gt;Stripe Subscriptions in Next.js 16: Complete SaaS Billing Guide (2026)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/22867/how-to-integrate-stripe-checkout-with-next-js-16-2026-edition</guid>
<pubDate>Fri, 17 Jul 2026 04:44:30 +0000</pubDate>
</item>
<item>
<title>WooCommerce Payment Gateway Integration Guide: Stripe, PayPal &amp; More</title>
<link>https://coderlegion.com/21047/woocommerce-payment-gateway-integration-guide-stripe-paypal-more</link>
<description>&lt;p&gt;Setting up payment gateways correctly is one of the most important steps in launching a WooCommerce store — get it wrong, and you lose sales at checkout before they ever complete. This guide covers the most common payment gateways for small-to-mid-level WooCommerce stores, how they compare, and what to watch out for during setup.&lt;/p&gt;
&lt;h3 id=&quot;what-payment-gateways-work-best-with-woocommerce-&quot;&gt;What Payment Gateways Work Best with WooCommerce?&lt;/h3&gt;
&lt;p&gt;WooCommerce supports dozens of payment gateways, but for most small and mid-sized stores, three options cover almost every use case: Stripe, PayPal, and WooCommerce Payments (built directly into WooCommerce). Each has different fee structures, checkout experiences, and country availability, so the right choice depends on where your customers are and how you want checkout to feel.&lt;/p&gt;
&lt;h3 id=&quot;how-do-you-set-up-stripe-with-woocommerce-&quot;&gt;How Do You Set Up Stripe with WooCommerce?&lt;/h3&gt;
&lt;p&gt;Stripe integrates with WooCommerce through the official WooCommerce Stripe Gateway plugin. After installing it, you connect your Stripe account via API keys (found in your Stripe dashboard under Developers &amp;gt; API Keys), enable the payment methods you want to accept (cards, Apple Pay, Google Pay), and configure your checkout to either redirect or use Stripe's embedded card fields for a smoother on-site experience.&lt;/p&gt;
&lt;p&gt;The biggest advantage of Stripe is that customers never leave your site to pay — this typically improves conversion rates compared to gateways that redirect to an external page.&lt;/p&gt;
&lt;h3 id=&quot;how-do-you-set-up-paypal-with-woocommerce-&quot;&gt;How Do You Set Up PayPal with WooCommerce?&lt;/h3&gt;
&lt;p&gt;PayPal offers a few integration options, but the most reliable for WooCommerce is the official WooCommerce PayPal Payments plugin. It supports both PayPal account payments and direct card payments without requiring the buyer to have a PayPal account, which removes a common checkout friction point.&lt;/p&gt;
&lt;p&gt;PayPal is worth keeping even alongside Stripe, since some customers specifically prefer or trust PayPal more, especially for higher-value B2B purchases.&lt;/p&gt;
&lt;h3 id=&quot;stripe-vs-paypal-which-should-you-use-&quot;&gt;Stripe vs PayPal: Which Should You Use?&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Factor &lt;/th&gt;&lt;th&gt;Stripe &lt;/th&gt;&lt;th&gt;PayPal&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Checkout experience &lt;/td&gt;&lt;td&gt;Stays on your site &lt;/td&gt;&lt;td&gt;Can redirect (or use card fields)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Transaction fees &lt;/td&gt;&lt;td&gt;~2.9% + $0.30 (varies by country) &lt;/td&gt;&lt;td&gt;~2.9% + $0.30 (varies by country)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Buyer trust &lt;/td&gt;&lt;td&gt;Strong, especially for cards &lt;/td&gt;&lt;td&gt;Very strong, especially internationally&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Setup complexity &lt;/td&gt;&lt;td&gt;Plugin + API keys &lt;/td&gt;&lt;td&gt;Plugin + business account&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Best for &lt;/td&gt;&lt;td&gt;Card-first, fast checkout &lt;/td&gt;&lt;td&gt;International buyers, B2B trust&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;For most stores, running both side by side gives customers a choice without adding much setup overhead — and it's a pattern we recommend for small-to-mid-level WooCommerce stores serving US, UK, and international customers.&lt;/p&gt;
&lt;h3 id=&quot;what-are-common-payment-gateway-integration-mistakes-&quot;&gt;What Are Common Payment Gateway Integration Mistakes?&lt;/h3&gt;
&lt;p&gt;The most common issues we see on WooCommerce stores are: leaving test/sandbox API keys live in production, not enabling SSL before processing live payments (which breaks PCI compliance), and not testing the full checkout flow on mobile before launch. Always run a full test transaction with each gateway before going live, and double-check your SSL certificate is active across the entire checkout flow.&lt;/p&gt;
&lt;h3 id=&quot;does-hosting-affect-payment-gateway-performance-&quot;&gt;Does Hosting Affect Payment Gateway Performance?&lt;/h3&gt;
&lt;p&gt;Yes — checkout speed and reliability depend heavily on server response time, especially during payment processing redirects and webhook callbacks. Slow or unreliable hosting can cause failed transactions or timeout errors at the worst possible moment: checkout. We recommend &lt;a rel=&quot;nofollow&quot; href=&quot;https://oceanwebhosting.com/dedicated-servers/&quot;&gt;OceanWebHosting Dedicated Servers&lt;/a&gt; for WooCommerce stores that need consistent uptime and fast response times during checkout.&lt;/p&gt;
&lt;h3 id=&quot;frequently-asked-questions-2&quot;&gt;Frequently Asked Questions&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Can I use both Stripe and PayPal on the same WooCommerce store?&lt;/strong&gt;&lt;br&gt;
Yes, and it's generally recommended. Offering both gives customers a choice and can reduce checkout abandonment from buyers who prefer one method over the other.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Do I need an SSL certificate for WooCommerce payment gateways?&lt;/strong&gt;&lt;br&gt;
Yes, SSL is required for PCI compliance and most payment gateways won't process live transactions without it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How much does it cost to set up payment gateway integration on WooCommerce?&lt;/strong&gt;&lt;br&gt;
For most small-to-mid-level stores, professional payment gateway integration (Stripe, PayPal, or both) typically starts from $400, depending on complexity and any custom checkout requirements.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Can a developer help if my payment gateway isn't working correctly?&lt;/strong&gt;&lt;br&gt;
Yes — common issues like failed webhooks, incorrect API key setup, or checkout errors are usually quick to diagnose and fix. See our guide on &lt;a rel=&quot;nofollow&quot; href=&quot;https://amanurrahman.com/hire-woocommerce-developer&quot;&gt;hiring a WooCommerce developer&lt;/a&gt; for help.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Originally published on &lt;a rel=&quot;nofollow&quot; href=&quot;https://amanurrahman.com/blog-post/woocommerce-payment-gateway-integration-guide&quot;&gt;amanurrahman.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/21047/woocommerce-payment-gateway-integration-guide-stripe-paypal-more</guid>
<pubDate>Sat, 20 Jun 2026 17:43:23 +0000</pubDate>
</item>
<item>
<title>How to Create a One-Time Payment Link and Secure Webhook Integration with Stripe</title>
<link>https://coderlegion.com/6894/how-to-create-a-one-time-payment-link-and-secure-webhook-integration-with-stripe</link>
<description>&lt;h3 id=&quot;introduction&quot;&gt;Introduction&lt;/h3&gt;
&lt;p&gt;Stripe is an online payment platform for securely accepting credit cards, bank transfers, and more.&lt;br&gt;
This guide demonstrates an end-to-end integration using &lt;strong&gt;Stripe Checkout Sessions&lt;/strong&gt; for one-time payments and a tamper-proof server webhook to confirm transactions.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;Terminology:&lt;/strong&gt; &quot;Payment Link&quot; here refers to the URL returned by a Stripe Checkout Session (not Stripe's no-code &quot;Payment Links&quot; product).&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h3 id=&quot;payment-flow-overview&quot;&gt;Payment Flow Overview&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Client clicks &lt;strong&gt;&quot;Pay Now&quot;&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Backend creates a unique &lt;strong&gt;Checkout Session&lt;/strong&gt; and returns the session URL.&lt;/li&gt;
&lt;li&gt;Client is redirected to Stripe, completes payment, and is redirected to your success URL.&lt;/li&gt;
&lt;li&gt;Stripe sends an asynchronous webhook (server-to-server) to your backend.&lt;/li&gt;
&lt;li&gt;Backend verifies the webhook signature, updates the database, and fulfills the order.&lt;/li&gt;
&lt;li&gt;Client gains access to the purchased resource.&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id=&quot;textual-diagram-ascii-&quot;&gt;Textual Diagram (ASCII)&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;Client (browser)
    |
    | Click &quot;Pay Now&quot;
    v
Backend (your server)
    - create Checkout Session -&amp;gt; returns session.url
    |
    | Redirect to Stripe Checkout
    v
Stripe Checkout (Hosted)
    - collects payment
    |                       \
    | Redirect to success_url   POST webhook -&amp;gt; /api/webhook
    v                       /
Client (success_url)       /
                          v
Backend Webhook Handler &amp;lt;-- verifies signature, idempotency, updates DB
    |
    v
Fulfillment / Grant access to client
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;quick-setup&quot;&gt;Quick Setup&lt;/h3&gt;
&lt;h4 id=&quot;1-install-packages&quot;&gt;1. Install packages&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm install stripe dotenv
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;2-env-example-&quot;&gt;2. .env (example)&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;STRIPE_SECRET_KEY=sk_test_...
STRIPE_WEBHOOK_SECRET=whsec_...
CLIENT_URL=http://localhost:5173
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;3-initialize-stripe&quot;&gt;3. Initialize Stripe&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;// src/utils/stripe.ts
import Stripe from 'stripe';
import 'dotenv/config';

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY as string,);

export default stripe;
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;create-checkout-session-backend-route-&quot;&gt;Create Checkout Session (Backend Route)&lt;/h3&gt;
&lt;p&gt;Create a route that makes a Checkout Session and returns &lt;code&gt;session.url&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;// routes/payment.ts
import { Request, Response } from 'express';
import stripe from '../utils/stripe';

export const createCheckoutSession = async (req: Request, res: Response) =&amp;gt; {
  const { id } = req.body;
  const existingProduct = { id, price: 4000, name: &quot;Hamza's Coffee&quot; };

  try {
    const session = await stripe.checkout.sessions.create({
      mode: 'payment',
      payment_method_types: ['card', 'link'],
      line_items: [{
        price_data: {
          currency: 'usd',
          product_data: { name: existingProduct.name, description: 'Payment for services rendered' },
          unit_amount: existingProduct.price,
        },
        quantity: 1,
      }],
      success_url: `${process.env.CLIENT_URL}/payment/success?session_id={CHECKOUT_SESSION_ID}`,
      cancel_url: `${process.env.CLIENT_URL}/payment/cancel`,
      metadata: { product_id: existingProduct.id }
    });

    res.status(201).json({ message: 'Payment Initialized Successfully', data: { url: session.url } });
  } catch (error) {
    res.status(500).json({ message: 'Error initializing payment.' });
  }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;webhook-raw-body-amp-signature-verification&quot;&gt;Webhook: Raw Body &amp;amp; Signature Verification&lt;/h3&gt;
&lt;p&gt;Define the webhook route &lt;strong&gt;before&lt;/strong&gt; &lt;code&gt;express.json()&lt;/code&gt; so the raw body can be used for signature verification.&lt;/p&gt;
&lt;h4 id=&quot;server-example&quot;&gt;Server Example&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;// server.ts
import express from 'express';
import bodyParser from 'body-parser';
import { webhookHandler } from './routes/webhook';
import { createCheckoutSession } from './routes/payment';

const app = express();

// Raw parser for webhook route only
app.post('/api/webhook', bodyParser.raw({ type: 'application/json' }), webhookHandler);

// Then JSON parser for all other routes
app.use(express.json());
app.post('/api/create-payment-link', createCheckoutSession);

app.listen(4000, () =&amp;gt; console.log('Server running on port 4000'));
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;webhook-handler&quot;&gt;Webhook Handler&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;// routes/webhook.ts
import { Request, Response } from 'express';
import Stripe from 'stripe';
import stripe from '../utils/stripe';
import dotenv from 'dotenv';
dotenv.config();

export const webhookHandler = async (req: Request, res: Response) =&amp;gt; {
  const sig = req.headers['stripe-signature'] as string;
  let event: Stripe.Event;

  try {
    event = stripe.webhooks.constructEvent(req.body, sig, process.env.STRIPE_WEBHOOK_SECRET!);
  } catch (err: any) {
    console.error('Webhook signature verification failed:', err.message);
    return res.status(400).send(`Webhook Error: ${err.message}`);
  }

  if (event.type === 'checkout.session.completed') {
    const session = event.data.object as Stripe.Checkout.Session;
    const productId = session.metadata?.product_id;

    if (productId &amp;amp;&amp;amp; session.payment_status === 'paid') {
      // IDEMPOTENCY: ensure session.id hasn't been processed already
      // Example: if (!db.processed(session.id)) { db.markProcessed(session.id); grantAccess(productId); }
      console.log(`Payment confirmed for product ID: ${productId}`);
    }
  }

  res.status(200).json({ received: true });
};
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;local-testing-with-stripe-cli&quot;&gt;Local Testing with Stripe CLI&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Forward events:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;stripe listen --forward-to localhost:4000/api/webhook
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Copy the &lt;code&gt;whsec_...&lt;/code&gt; key from the CLI output into &lt;code&gt;.env&lt;/code&gt; as &lt;code&gt;STRIPE_WEBHOOK_SECRET&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Trigger event:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;stripe trigger checkout.session.completed
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;deployment-checklist&quot;&gt;Deployment Checklist&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Deploy backend to production.&lt;/li&gt;
&lt;li&gt;Register your live webhook URL in Stripe Dashboard (Developers → Webhooks) and subscribe to &lt;code&gt;checkout.session.completed&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Set &lt;code&gt;STRIPE_SECRET_KEY&lt;/code&gt; and &lt;code&gt;STRIPE_WEBHOOK_SECRET&lt;/code&gt; in your production environment.&lt;/li&gt;
&lt;li&gt;Ensure idempotency checks on webhook processing.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;rationale-key-decisions&quot;&gt;Rationale / Key Decisions&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Raw body required so Stripe signature can be verified.&lt;/li&gt;
&lt;li&gt;Signature verification prevents forged events.&lt;/li&gt;
&lt;li&gt;Idempotency avoids duplicate fulfillment on retries.&lt;/li&gt;
&lt;li&gt;Stripe CLI simplifies local testing.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;further-reading&quot;&gt;Further Reading&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Refunds: &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.stripe.com/refunds&quot;&gt;Stripe Refunds&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Subscriptions: &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.stripe.com/recurring-payments&quot;&gt;Stripe Recurring Payments&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;API Reference: &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.stripe.com/api&quot;&gt;Stripe API&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
</description>
<category>TypeScript</category>
<guid isPermaLink="true">https://coderlegion.com/6894/how-to-create-a-one-time-payment-link-and-secure-webhook-integration-with-stripe</guid>
<pubDate>Mon, 27 Oct 2025 12:06:28 +0000</pubDate>
</item>
<item>
<title>pawaPay PHP SDK v4.4.0 adds dual V1/V2 support</title>
<link>https://coderlegion.com/4629/pawapay-php-sdk-v4-4-0-adds-dual-v1-v2-support</link>
<description>&lt;h2 id=&quot;pawapay-php-sdk-v4-4-0-dual-version-support-v1-v2-seamless-migration&quot;&gt;&lt;strong&gt;pawaPay PHP SDK v4.4.0, Dual Version Support [V1 + V2], Seamless Migration&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;pawaPay helps teams accept and move money across African markets with a clean API surface. In &lt;strong&gt;v4.4.0&lt;/strong&gt; of the &lt;strong&gt;pawaPay PHP SDK&lt;/strong&gt;, we introduced &lt;strong&gt;dual version support&lt;/strong&gt; so &lt;strong&gt;V1&lt;/strong&gt; and &lt;strong&gt;V2&lt;/strong&gt; can live together in one codebase. Think of it as two gears turning in sync, V1 for stability, V2 for velocity.&lt;/p&gt;
&lt;p&gt;This design gives you the freedom to adopt V2 where it shines, while keeping proven V1 flows earning in production.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;table-of-contents&quot;&gt;Table of contents&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Why dual version support&lt;/li&gt;
&lt;li&gt;What changed in v4.4.0&lt;/li&gt;
&lt;li&gt;Environment setup and configuration&lt;/li&gt;
&lt;li&gt;Full runnable examples, V1 and V2&lt;/li&gt;
&lt;li&gt;Hosted Payment Page, end to end&lt;/li&gt;
&lt;li&gt;Verification flow, returnUrl and verify without cURL&lt;/li&gt;
&lt;li&gt;Testing and safety nets with PHPUnit&lt;/li&gt;
&lt;li&gt;Migration playbook, step by step&lt;/li&gt;
&lt;li&gt;Edge cases, country notes, and logging&lt;/li&gt;
&lt;li&gt;Troubleshooting guide&lt;/li&gt;
&lt;li&gt;Security notes and hardening tips&lt;/li&gt;
&lt;li&gt;Where to get it&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h3 id=&quot;1-why-dual-version-support&quot;&gt;1) Why dual version support&lt;/h3&gt;
&lt;p&gt;Upgrading payments is risky. Teams struggle with three realities:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;All or nothing switches increase outage risk.&lt;/li&gt;
&lt;li&gt;Two SDKs mean duplicate configs and twice the drift.&lt;/li&gt;
&lt;li&gt;Testing in production is scary without a clean rollback.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Our approach&lt;/strong&gt;: keep &lt;strong&gt;V1&lt;/strong&gt; as the default, allow &lt;strong&gt;V2&lt;/strong&gt; to be toggled by environment or per call. You get gradual rollouts, quick comparisons, and instant fallback.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;2-what-changed-in-v4-4-0&quot;&gt;2) What changed in v4.4.0&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Version aware client&lt;/strong&gt;. Pass &lt;code&gt;v1&lt;/code&gt; or &lt;code&gt;v2&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Hosted Payment Page&lt;/strong&gt; usable from both versions.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Split config files&lt;/strong&gt; for clarity: &lt;code&gt;active_conf_v1.json&lt;/code&gt;, &lt;code&gt;active_conf_v2.json&lt;/code&gt;, plus versioned MNO availability JSONs.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Better failure mapping&lt;/strong&gt; via an improved &lt;code&gt;FailureCodeHelper&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cleaner errors&lt;/strong&gt; that mention the version expectation.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;3-environment-setup-and-configuration&quot;&gt;3) Environment setup and configuration&lt;/h3&gt;
&lt;p&gt;Create a &lt;code&gt;.env&lt;/code&gt; with clear tokens per environment.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-dotenv&quot;&gt;# .env
ENVIRONMENT=sandbox
PAWAPAY_SANDBOX_API_TOKEN=your_sandbox_api_token_here
PAWAPAY_PRODUCTION_API_TOKEN=your_production_api_token_here

# Choose default API version across your app
PAWAPAY_API_VERSION=v1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Composer install:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;composer require katorymnd/pawa-pay-integration
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;4-full-runnable-examples-v1-and-v2&quot;&gt;4) Full runnable examples, V1 and V2&lt;/h3&gt;
&lt;h4 id=&quot;a-minimal-v1-example-server-side-initiation&quot;&gt;A) Minimal V1 example, server side initiation&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;&amp;lt;?php
/**
 * Example: Minimal V1 deposit initiation
 * Goal, show a working flow with V1 as default.
 * Notes, reads ENV, builds a V1 client, initiates a classic deposit call.
 */

require_once __DIR__ . '/../vendor/autoload.php';

use Dotenv\Dotenv;
use Katorymnd\PawaPayIntegration\Api\ApiClient;

$dotenv = Dotenv::createImmutable(__DIR__ . '/..');
$dotenv-&amp;gt;load();

$environment = getenv('ENVIRONMENT') ?: 'sandbox';
$sslVerify   = ($environment === 'production');
$apiTokenKey = 'PAWAPAY_' . strtoupper($environment) . '_API_TOKEN';
$apiToken    = $_ENV[$apiTokenKey] ?? null;

if (!$apiToken) {
    http_response_code(500);
    echo 'Missing API token';
    exit;
}

$client = new ApiClient($apiToken, $environment, $sslVerify, 'v1');

// Payload shape may differ per version, keep V1 fields here
$resp = $client-&amp;gt;initiateDeposit([
    'amount'      =&amp;gt; '15000',
    'currency'    =&amp;gt; 'UGX',
    'phoneNumber' =&amp;gt; '256753456789',
    'mno'         =&amp;gt; 'MTN',
    'reason'      =&amp;gt; 'Order #1234',
]);

header('Content-Type: application/json');
echo json_encode($resp, JSON_PRETTY_PRINT);
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;b-minimal-v2-example-auto-flow&quot;&gt;B) Minimal V2 example, auto flow&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;&amp;lt;?php
/**
 * Example: Minimal V2 deposit initiation (auto)
 * Goal, exercise the new V2 route without touching V1 code.
 * Notes, just switch the version to v2.
 */

require_once __DIR__ . '/../vendor/autoload.php';

use Dotenv\Dotenv;
use Katorymnd\PawaPayIntegration\Api\ApiClient;

$dotenv = Dotenv::createImmutable(__DIR__ . '/..');
$dotenv-&amp;gt;load();

$environment = getenv('ENVIRONMENT') ?: 'sandbox';
$sslVerify   = ($environment === 'production');
$apiTokenKey = 'PAWAPAY_' . strtoupper($environment) . '_API_TOKEN';
$apiToken    = $_ENV[$apiTokenKey] ?? null;

if (!$apiToken) {
    http_response_code(500);
    echo 'Missing API token';
    exit;
}

$client = new ApiClient($apiToken, $environment, $sslVerify, 'v2');

// V2 supports initiateDepositAuto which may return a redirect, or a ready status
$resp = $client-&amp;gt;initiateDepositAuto([
    'amount'      =&amp;gt; '15000',
    'currency'    =&amp;gt; 'UGX',
    'phoneNumber' =&amp;gt; '256753456789',
    'mno'         =&amp;gt; 'MTN',
    'returnUrl'   =&amp;gt; 'https://example.com/paymentProcessed',
]);

header('Content-Type: application/json');
echo json_encode($resp, JSON_PRETTY_PRINT);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Why both examples matter&lt;/strong&gt;&lt;br&gt;
You can test V2 side by side, compare payloads and responses, and fall back to V1 with a single parameter change.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;5-hosted-payment-page-end-to-end&quot;&gt;5) Hosted Payment Page, end to end&lt;/h3&gt;
&lt;p&gt;The hosted page is the fastest way to collect a payment with one redirect.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;&amp;lt;?php
/**
 * Example: Hosted Payment Page, works with V1 or V2
 * Goal, show a clean redirect based on initiateDepositAuto.
 */

require_once __DIR__ . '/../vendor/autoload.php';

use Dotenv\Dotenv;
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use Katorymnd\PawaPayIntegration\Api\ApiClient;
use Katorymnd\PawaPayIntegration\Utils\Helpers;

$dotenv = Dotenv::createImmutable(__DIR__ . '/..');
$dotenv-&amp;gt;load();

$environment = getenv('ENVIRONMENT') ?: 'sandbox';
$sslVerify   = ($environment === 'production');
$apiVersion  = getenv('PAWAPAY_API_VERSION') ?: 'v1';
$apiTokenKey = 'PAWAPAY_' . strtoupper($environment) . '_API_TOKEN';
$apiToken    = $_ENV[$apiTokenKey] ?? null;

if (!$apiToken) {
    http_response_code(500);
    echo 'Missing API token';
    exit;
}

$log = new Logger('pawaPay');
$log-&amp;gt;pushHandler(new StreamHandler(__DIR__ . '/../logs/payment_success.log', \Monolog\Level::Info));
$log-&amp;gt;pushHandler(new StreamHandler(__DIR__ . '/../logs/payment_failed.log',  \Monolog\Level::Error));

$client = new ApiClient($apiToken, $environment, $sslVerify, $apiVersion);

// Inputs, in real apps take these from your form
$depositId            = Helpers::generateUniqueId();
$amount               = '15000';
$currency             = 'UGX';
$msisdn               = '256753456789';
$mno                  = 'MTN';
$returnUrl            = 'https://your-app.example/returnUrl.php';
$statementDescription = 'Order 1234';

// V2 will typically return a redirect URL for the hosted page
try {
    $resp = $client-&amp;gt;initiateDepositAuto([
        'amount'      =&amp;gt; $amount,
        'currency'    =&amp;gt; $currency,
        'phoneNumber' =&amp;gt; $msisdn,
        'mno'         =&amp;gt; $mno,
        'returnUrl'   =&amp;gt; $returnUrl,
        'reason'      =&amp;gt; $statementDescription,
        'metadata'    =&amp;gt; ['depositId' =&amp;gt; $depositId],
    ]);

    if (!empty($resp['redirectUrl'])) {
        header('Location: ' . $resp['redirectUrl']);
        exit;
    }

    // If no redirect is required, log and show the result
    $log-&amp;gt;info('Hosted page not required', ['resp' =&amp;gt; $resp]);
    header('Content-Type: application/json');
    echo json_encode($resp, JSON_PRETTY_PRINT);
} catch (\Throwable $e) {
    $log-&amp;gt;error('Hosted page error', ['ex' =&amp;gt; $e-&amp;gt;getMessage()]);
    http_response_code(500);
    echo 'Payment initiation failed';
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;6-verification-flow-returnurl-and-verify-without-curl&quot;&gt;6) Verification flow, returnUrl and verify without cURL&lt;/h3&gt;
&lt;p&gt;Many teams prefer not to make HTTP calls to a sibling script. You can include the verifier directly.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;verify.php&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;&amp;lt;?php
/**
 * verify.php
 * Purpose, encapsulate verification logic that can be included from returnUrl.php
 * Input, expects $client and $transactionId to be in scope when included
 * Output, returns associative array $verification
 */

if (!isset($client) || !isset($transactionId)) {
    throw new \RuntimeException('verify.php expects $client and $transactionId');
}

$verification = $client-&amp;gt;checkTransactionStatus([
    'transactionId' =&amp;gt; $transactionId,
]);

// The parent script will read $verification
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;returnUrl.php&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;&amp;lt;?php
/**
 * returnUrl.php
 * Purpose, handle redirected users after payment, then verify internally
 */

require_once __DIR__ . '/../vendor/autoload.php';

use Dotenv\Dotenv;
use Katorymnd\PawaPayIntegration\Api\ApiClient;

$dotenv = Dotenv::createImmutable(__DIR__ . '/..');
$dotenv-&amp;gt;load();

$environment = getenv('ENVIRONMENT') ?: 'sandbox';
$sslVerify   = ($environment === 'production');
$apiVersion  = getenv('PAWAPAY_API_VERSION') ?: 'v2';
$apiTokenKey = 'PAWAPAY_' . strtoupper($environment) . '_API_TOKEN';
$apiToken    = $_ENV[$apiTokenKey] ?? null;

$client = new ApiClient($apiToken, $environment, $sslVerify, $apiVersion);

// Transaction id from provider redirect or your metadata
$transactionId = $_GET['txId'] ?? $_POST['txId'] ?? null;

if (!$transactionId) {
    http_response_code(400);
    echo 'Missing transactionId';
    exit;
}

// Include verifier in-process, no cURL required
$verification = [];
require __DIR__ . '/verify.php';

// Persist result, show receipt, or redirect to success page
header('Content-Type: application/json');
echo json_encode(['verified' =&amp;gt; true, 'result' =&amp;gt; $verification], JSON_PRETTY_PRINT);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Why this is safe and simple&lt;/strong&gt;&lt;br&gt;
You keep the verification logic pure PHP, no extra HTTP hops, fewer moving parts, clearer stack traces.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;7-testing-and-safety-nets-with-phpunit&quot;&gt;7) Testing and safety nets with PHPUnit&lt;/h3&gt;
&lt;p&gt;A tiny guard to ensure your app respects the chosen version.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;&amp;lt;?php
/**
 * tests/VersionSelectionTest.php
 * Purpose, assert the client honors PAWAPAY_API_VERSION, prevent regressions.
 */

use PHPUnit\Framework\TestCase;
use Katorymnd\PawaPayIntegration\Api\ApiClient;

final class VersionSelectionTest extends TestCase
{
    public function testChoosesV2WhenEnvSaysV2(): void
    {
        $client = new ApiClient('fake', 'sandbox', false, 'v2');
        $this-&amp;gt;assertTrue(method_exists($client, 'initiateDepositAuto'), 'V2 method should exist');
    }

    public function testChoosesV1WhenEnvSaysV1(): void
    {
        $client = new ApiClient('fake', 'sandbox', false, 'v1');
        $this-&amp;gt;assertTrue(method_exists($client, 'initiateDeposit'), 'V1 method should exist');
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Add more tests for response shape, error handling, and idempotency.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;8-migration-playbook-step-by-step&quot;&gt;8) Migration playbook, step by step&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Inventory&lt;/strong&gt; your live flows, deposit, payout, refund, reconciliation.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Enable dual version&lt;/strong&gt; globally with &lt;code&gt;PAWAPAY_API_VERSION=v1&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Stand up V2&lt;/strong&gt; in sandbox, run the minimal V2 example.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Mirror payloads&lt;/strong&gt; and compare responses, store both logs.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Dark launch&lt;/strong&gt; V2 for a small traffic slice, or specific markets.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Monitor&lt;/strong&gt; failure codes with &lt;code&gt;FailureCodeHelper&lt;/code&gt;, alert on deltas.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Flip&lt;/strong&gt; a single product line to V2, observe for one full billing cycle.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rollback rule&lt;/strong&gt; documented, set the env back to &lt;code&gt;v1&lt;/code&gt; if anything spikes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Expand&lt;/strong&gt; V2 to more flows after two stable weeks.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Retire&lt;/strong&gt; V1 only when every flow is stable in V2.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Alternative to env flags&lt;/strong&gt;&lt;br&gt;
Use a &lt;strong&gt;feature flag service&lt;/strong&gt; or a &lt;strong&gt;per request switch&lt;/strong&gt;. Env is simple, flags give you finer control per tenant, market, or route.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;9-edge-cases-country-notes-and-logging&quot;&gt;9) Edge cases, country notes, and logging&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;MNO requirements differ&lt;/strong&gt;. Some providers are strict on MSISDN formatting, always sanitize to digits and include country code.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Currencies vary&lt;/strong&gt;. Lock your currency per market in config, do not trust form inputs.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Retrials&lt;/strong&gt;. Use idempotent keys where available, or your own &lt;code&gt;depositId&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Logs by version&lt;/strong&gt;. Write to &lt;code&gt;payment_v1.log&lt;/code&gt; and &lt;code&gt;payment_v2.log&lt;/code&gt; so comparisons are easy.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Metadata discipline&lt;/strong&gt;. Pass your order or cart reference to simplify reconciliation.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;10-troubleshooting-guide&quot;&gt;10) Troubleshooting guide&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Missing API token&lt;/strong&gt;, check &lt;code&gt;.env&lt;/code&gt; names and &lt;code&gt;ENVIRONMENT&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Method not found&lt;/strong&gt; when calling V2, confirm you constructed the client with &lt;code&gt;v2&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Unexpected currency error&lt;/strong&gt;, confirm ISO code and market pairing.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Callback not firing&lt;/strong&gt;, verify your &lt;code&gt;returnUrl&lt;/code&gt; is public and uses HTTPS in production.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Random declines&lt;/strong&gt;, try a different MNO in sandbox, then compare failure codes with &lt;code&gt;FailureCodeHelper&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;11-security-notes-and-hardening-tips&quot;&gt;11) Security notes and hardening tips&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Keep tokens outside your repo, use &lt;code&gt;.env&lt;/code&gt; and secret managers.&lt;/li&gt;
&lt;li&gt;Verify all incoming callbacks, do not rely only on query params, call &lt;code&gt;checkTransactionStatus&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Store only what you need, mask MSISDN in logs, rotate tokens on schedule.&lt;/li&gt;
&lt;li&gt;Enforce TLS, set &lt;code&gt;sslVerify=true&lt;/code&gt; in production.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;12-where-to-get-it&quot;&gt;12) Where to get it&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;GitHub&lt;/strong&gt;, &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/katorymnd/pawa-pay-integration&quot;&gt;https://github.com/katorymnd/pawa-pay-integration&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Composer&lt;/strong&gt;,&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;composer require katorymnd/pawa-pay-integration
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h4 id=&quot;closing-note&quot;&gt;Closing note&lt;/h4&gt;
&lt;p&gt;Dual version support is a calm bridge, not a cliff. Start with V1, test V2 where it counts, and move forward with confidence. If you want a diagram of the V1 to V2 rollout path, say the word and I will add a crisp visual.&lt;/p&gt;
</description>
<category>Full-stack Dev</category>
<guid isPermaLink="true">https://coderlegion.com/4629/pawapay-php-sdk-v4-4-0-adds-dual-v1-v2-support</guid>
<pubDate>Tue, 12 Aug 2025 19:24:36 +0000</pubDate>
</item>
<item>
<title>pawaPay SDK offers secure, user-friendly mobile money integration for PHP, ideal for African e-commerce.</title>
<link>https://coderlegion.com/454/pawapay-sdk-offers-secure-user-friendly-mobile-money-integration-ideal-african-commerce</link>
<description>&lt;h2&gt;pawaPay Payment SDK: Streamline Your Payment Processes in PHP&lt;/h2&gt;
&lt;p&gt;In the fast-paced world of &lt;strong&gt;e-commerce and online transactions&lt;/strong&gt;, efficiency, security, and simplicity are paramount. Integrating payment solutions can often be a complex and time-consuming process, especially when dealing with multiple payment methods across different regions. The &lt;strong&gt;pawaPay Payment SDK&lt;/strong&gt; emerges as a robust solution, specially tailored for PHP applications, aiming to streamline the integration of pawaPay's Mobile Money payment solutions. This lightweight, easy-to-use SDK encapsulates powerful functionalities into a developer-friendly package, enabling seamless payment processes within your applications.&lt;/p&gt;
&lt;h3&gt;What is pawaPay?&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;pawaPay&lt;/strong&gt; is a &lt;strong&gt;mobile money payment platform&lt;/strong&gt; designed to empower businesses to seamlessly send and receive payments across various African countries using mobile wallets. With over &lt;strong&gt;300 million mobile money users&lt;/strong&gt; in Africa, pawaPay taps into this expansive ecosystem, providing rapid settlements, eliminating chargebacks, and ensuring secure transaction processing—all accessible from a unified dashboard. pawaPay offers integrations with all major mobile money operators, delivering a scalable and efficient solution for businesses aiming to leverage the burgeoning mobile money market in Africa.&lt;/p&gt;
&lt;h3&gt;Why Mobile Money in Africa?&lt;/h3&gt;
&lt;p&gt;Mobile money has revolutionized financial transactions in Africa, where traditional banking infrastructure is limited. It allows users to perform financial transactions using their mobile phones, without needing a bank account. This has significantly increased financial inclusion and has become the preferred method of payment for many Africans. By integrating mobile money into your applications, you open your business to a vast and growing market.&lt;/p&gt;
&lt;h2&gt;Introducing the pawaPay Payment SDK&lt;/h2&gt;
&lt;p&gt;The &lt;strong&gt;pawaPay Payment SDK&lt;/strong&gt; is a secure and straightforward way to integrate pawaPay's mobile money payment services into PHP applications. By handling the complexities of direct API calls and providing a set of easy-to-implement methods, this SDK allows developers to efficiently process payments without needing deep expertise in payment systems.&lt;/p&gt;
&lt;h3&gt;Key Benefits&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Simplified Integration&lt;/strong&gt;: Reduce development time with an SDK that abstracts complex API interactions.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Enhanced Security&lt;/strong&gt;: Leverage built-in security features adhering to the latest standards.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Comprehensive Functionality&lt;/strong&gt;: Access a wide range of payment operations through simple methods.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Key Features of the pawaPay Payment SDK&lt;/h2&gt;
&lt;h3&gt;Easy to Use&lt;/h3&gt;
&lt;p&gt;With comprehensive documentation and ready-to-use code snippets, integrating the pawaPay SDK into your existing PHP projects is straightforward. The SDK reduces the complexity involved in coding for mobile money payment systems, allowing you to focus on other critical aspects of your application.&lt;/p&gt;
&lt;h3&gt;Security at Its Core&lt;/h3&gt;
&lt;p&gt;Built with security as a priority, the SDK adheres to the latest standards in data encryption, validations, and other security protocols to ensure that all transactions are protected against fraud and breaches.&lt;/p&gt;
&lt;h3&gt;Comprehensive Payment Functions&lt;/h3&gt;
&lt;p&gt;Whether you're looking to &lt;strong&gt;initiate deposits&lt;/strong&gt;, &lt;strong&gt;manage payouts&lt;/strong&gt;, &lt;strong&gt;process refunds&lt;/strong&gt;, or &lt;strong&gt;check transaction statuses&lt;/strong&gt;, the SDK covers all bases, providing you with the tools you need to manage various mobile money payment operations efficiently.&lt;/p&gt;
&lt;h3&gt;Cross-Network Compatibility&lt;/h3&gt;
&lt;p&gt;The SDK supports integration with all major mobile money operators in Africa, ensuring that your application can process payments regardless of the user's mobile money provider.&lt;/p&gt;
&lt;h2&gt;System Requirements&lt;/h2&gt;
&lt;p&gt;Before you begin, ensure that your environment meets the following system requirements:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;PHP Version&lt;/strong&gt;: 8.0 or higher&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Composer&lt;/strong&gt;: PHP dependency manager installed&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;cURL&lt;/strong&gt;: Required for making HTTP requests&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;OpenSSL&lt;/strong&gt;: For secure HTTPS connections&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;JSON Extension&lt;/strong&gt;: Enabled in PHP for working with JSON data&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SSL Certification&lt;/strong&gt;: A valid SSL certificate on your server&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Web Server&lt;/strong&gt;: Apache 2.4, LiteSpeed V8.1, or any modern server capable of running PHP&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Installation and Setup&lt;/h2&gt;
&lt;h3&gt;Step 1: Install the SDK via Composer&lt;/h3&gt;
&lt;p&gt;Begin by installing the SDK using Composer:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;composer require katorymnd/pawa-pay-integration
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Step 2: Configure Your API Keys&lt;/h3&gt;
&lt;p&gt;After installation, you need to configure your API keys provided by pawaPay. You can set them in your application's environment variables.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;require_once 'vendor/autoload.php';

use Dotenv\Dotenv;
use Katorymnd\PawaPayIntegration\Api\ApiClient;
use Katorymnd\PawaPayIntegration\Utils\Helpers;
use Katorymnd\PawaPayIntegration\Utils\Validator;
use Katorymnd\PawaPayIntegration\Utils\FailureCodeHelper;
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use Whoops\Run;
use Whoops\Handler\PrettyPageHandler;

// Initialize Whoops error handler for development
$whoops = new Run();
$whoops-&amp;gt;pushHandler(new PrettyPageHandler());
$whoops-&amp;gt;register();

// Load the environment variables from the .env file
$dotenv = Dotenv::createImmutable(__DIR__ . '/../');
$dotenv-&amp;gt;load();


// Set the environment and SSL verification based on the production status
$environment = getenv('ENVIRONMENT') ?: 'sandbox'; // Default to sandbox if not specified
$sslVerify = $environment === 'production';  // SSL verification true in production

// Dynamically construct the API token key
$apiTokenKey = 'PAWAPAY_' . strtoupper($environment) . '_API_TOKEN';

// Get the API token based on the environment
$apiToken = $_ENV[$apiTokenKey] ?? null;


if (!$apiToken) {
    echo json_encode([
        'success' =&amp;gt; false,
        'errorMessage' =&amp;gt; 'API token not found for the selected environment.'
    ]);
    exit;
}

// Initialize Monolog for logging
$log = new Logger('pawaPayLogger');
$log-&amp;gt;pushHandler(new StreamHandler(__DIR__ . '/../logs/payment_success.log', \Monolog\Level::Info));
$log-&amp;gt;pushHandler(new StreamHandler(__DIR__ . '/../logs/payment_failed.log', \Monolog\Level::Error));

// Create a new instance of the API client with SSL verification control
$pawaPayClient = new ApiClient($apiToken, $environment, $sslVerify);

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;Replace &lt;code&gt;'YOUR_PAWAPAY_API_KEY'&lt;/code&gt; with your actual API key provided by pawaPay in your '.env file.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;Using the SDK&lt;/h2&gt;
&lt;h3&gt;Initiating a Payment&lt;/h3&gt;
&lt;p&gt;To initiate a payment, you can use the &lt;code&gt;initiateDeposit&lt;/code&gt; method. Here's how:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;



// Initialize the API client
$pawaPayClient = new ApiClient($apiToken, $environment, $sslVerify);

// Generate a unique transaction ID (UUID v4)
$transactionId = Helpers::generateUniqueId();

// Define payment details
$amount = '1000'; // Payment amount in KES (Kenyan Shilling)
$currency = 'KES'; // Currency code
$correspondent = 'MPESA_KEN'; // Correspondent ID for the MPESA network
$recipientMsisdn = '254712345678'; // Recipient's phone number

// Custom transaction description
$description = 'Payment for invoice -1234';

// Optional metadata for the transaction
$metadata = [
    'order_id' =&amp;gt; '1234',
    'customer_id' =&amp;gt; '5678',
];

// Step 1: Validate the amount
$validatedAmount = Validator::symfonyValidateAmount($amount);

// Step 2: Validate the description
$validatedDescription = Validator::validateStatementDescription($description);

// Step 3: Validate metadata if provided
if (!empty($metadata)) {
    Validator::validateMetadataItemCount($metadata);
}

// Step 4: Initiate the payment
$response = $pawaPayClient-&amp;gt;initiatePayment(
    $transactionId,
    $validatedAmount,
    $currency,
    $correspondent,
    $recipientMsisdn,
    $validatedDescription,
    $metadata
);

// Step 5: Check if the response is successful
if ($response['status'] === 200) {
    // Payment was initiated successfully
    $log-&amp;gt;info('Payment initiated successfully', [
        'transactionId' =&amp;gt; $transactionId,
        'response' =&amp;gt; $response['response'],
    ]);
    echo &quot;Payment initiation successful. Transaction ID: $transactionId&quot;;
} else {
    // Payment initiation failed; handle the error
    $log-&amp;gt;error('Payment initiation failed', [
        'transactionId' =&amp;gt; $transactionId,
        'error' =&amp;gt; $response-&amp;gt;getErrorMessage(),
    ]);
    echo &quot;Payment initiation failed. Error: &quot; . $response-&amp;gt;getErrorMessage();
}


&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;In this example, we initiate a payment of 1000 KES to a recipient on the MPESA network. We check if the response is successful and handle it accordingly.&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;Checking Transaction Status&lt;/h3&gt;
&lt;p&gt;To check the status of a transaction:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;
 // Proceed to check the transaction status
        $statusResponse = $pawaPayClient-&amp;gt;checkTransactionStatus($transactionId, 'deposit');

        if ($statusResponse['status'] === 200) {
            $depositInfo = $statusResponse['response'][0]; // Get the deposit info
            $depositStatus = $depositInfo['status'];
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;Replace &lt;code&gt;'TRANSACTION_ID_RECEIVED'&lt;/code&gt; with the transaction ID you received when initiating the payment.&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;Handling Errors&lt;/h3&gt;
&lt;p&gt;The SDK is designed to handle errors gracefully. Here's how you might handle exceptions:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;try {
     $response = $pawaPayClient-&amp;gt;initiateDeposit(
        $depositId,
        $validatedAmount,
        $currency,
        $mno,
        $payerMsisdn,
        $validatedDescription,
        $metadata
    );

    // Process response
} catch (Exception $e) {
    // Log error details
    error_log('Payment initiation error: ' . $e-&amp;gt;getMessage());
    // Inform the user
    echo &quot;An error occurred while processing your payment. Please try again.&quot;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;Using try-catch blocks allows you to catch exceptions thrown by the SDK and handle them appropriately.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;Best Practices&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Secure API Keys&lt;/strong&gt;: Never expose your API keys in your code repositories. Use environment variables or secure configuration files.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Validate User Input&lt;/strong&gt;: Always validate and sanitize user input to prevent injection attacks.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Error Logging&lt;/strong&gt;: Implement robust error logging to aid in troubleshooting.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Transaction Monitoring&lt;/strong&gt;: Regularly monitor transactions to detect any suspicious activity.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Use Cases&lt;/h2&gt;
&lt;h3&gt;E-commerce Platforms&lt;/h3&gt;
&lt;p&gt;Integrate seamless payment solutions that cater to both sellers and buyers. With pawaPay, customers can pay using their preferred mobile money provider, increasing conversion rates.&lt;/p&gt;
&lt;h3&gt;Marketplaces&lt;/h3&gt;
&lt;p&gt;Facilitate secure and reliable transactions between multiple parties with ease. The SDK supports splitting payments and managing payouts to different vendors.&lt;/p&gt;
&lt;h3&gt;Donation Platforms&lt;/h3&gt;
&lt;p&gt;Allow donors to contribute easily via mobile money, broadening your reach to users without traditional banking services.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;The &lt;strong&gt;pawaPay Payment SDK&lt;/strong&gt; for PHP simplifies the integration of mobile money payment solutions, enabling businesses to tap into the vast African market easily. By abstracting the complexities of payment processing and providing robust security features, the SDK allows developers to focus on building great applications while ensuring seamless and secure transactions.&lt;/p&gt;
&lt;p&gt;Whether you're an e-commerce platform, a subscription service, or a marketplace, integrating pawaPay can significantly enhance your payment processing capabilities, reduce development time, and provide a better user experience.&lt;/p&gt;
&lt;h2&gt;Get Started Today&lt;/h2&gt;
&lt;p&gt;Visit the &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/katorymnd/pawa-pay-integration&quot;&gt;pawaPay Payment SDK GitHub Repository&lt;/a&gt; to access the SDK, documentation, and examples. Start integrating and unlock new opportunities in the African mobile money ecosystem.&lt;/p&gt;
&lt;h2&gt;References&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://pawapay.io/&quot;&gt;pawaPay Official Website&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.pawapay.io/&quot;&gt;pawaPay API Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/katorymnd/pawa-pay-integration&quot;&gt;pawaPay Payment SDK on GitHub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt; &lt;/p&gt;
&lt;hr&gt;
</description>
<category>Coding</category>
<guid isPermaLink="true">https://coderlegion.com/454/pawapay-sdk-offers-secure-user-friendly-mobile-money-integration-ideal-african-commerce</guid>
<pubDate>Mon, 25 Nov 2024 22:51:21 +0000</pubDate>
</item>
</channel>
</rss>