SkillAgentSearch skills...

PayHook

The simplest payment processing Java library in the world. Unifies PayPal and Stripe into one API. Supports both regular payments and subscriptions, with NO need of handling json, requests, storage into a database yourself.

Install / Use

/learn @Osiris-Team/PayHook

README

PayHook

The simplest payment processing Java library in the world. Unifies PayPal and Stripe into one API. Supports both regular payments and subscriptions, with NO need of handling json, requests, storage into a database yourself. Click here for Maven/Gradle/Sbt/Leinigen instructions. Java 8+ required. Make sure to watch this repository to get notified of future updates. Support and chat over at Discord.

Funding

I am actively maintaining this repository, publishing new releases and working on its codebase for free, so if this project benefits you and/or your company consider donating a monthly amount you seem fit. Thank you!

<a href="https://www.paypal.com/donate?hosted_button_id=JNXQCWF2TF9W4"><img src="https://github.com/andreostrovsky/donate-with-paypal/raw/master/blue.svg" height="40"></a>

Aim

Working with payments in Java is painful. If you want to expand to other third-party payment processors it's hell, that's why PayHook exists.

  void onBuyBtnClick() throws Exception {
    Payment payment = PayHook.expectPayment("USER_ID", myProduct, PaymentProcessor.PAYPAL,
          authorizedPayment -> {
            // Executed when payment was authorized by buyer
          });
    // Forward your user to complete/authorize the payment here...
    openUrl(payment.url);
  }

PayHooks' main goal is simplicity, thus there are only 3 important classes (PayHook | Product | Payment). The PayHook class contains most of the important static methods.

Features

  • Support for regular products and products with recurring payments (subscriptions).
  • Currently, works only via Webhooks.
  • Supported payment processors: PayPal and Stripe (supports ApplePay, GooglePay etc.).
  • Easy product/payment creation across multiple payment-processors, through abstraction.
  • No need to handle JSON or learn the individual REST-APIs of the payment-processors.
  • Secure, verified payments without duplicates, due to the design being based solely on validated webhook events.
  • Catch all payments. If your application is offline for example payment processors notice that the webhook event wasn't received and try again later, several times.
  • Actions on missed payments. Cancel the users' subscription if the amount due was not paid.
  • Validate PayPals Webhook events/notifications.
  • Low-level SQL queries to ensure maximum speed.
  • Payments saved to the database. This saves you time when creating summaries or tax reports, since all payments are at one place and not scattered over each payment processor.
  • Different database in sandbox mode, to ensure live and sandbox actions are separated strictly.
  • Commandline tool to extract relevant data from the database and modify it.

Todo

  • Make webhook requirement optional by doing periodic checks.
  • Functionality to send payments. Currently, it's only possible to receive payments from customers.
  • Add support for real goods. Currently, the focus is on digital goods and services, which means that billing addresses are ignored.
  • Add support for more payment processors.
  • Run as server (payments-server) since currently this software is only meant for Java developers to integrate into their existing projects.

Testing

You probably want to test this before running it in a production environment. You can do that easily from your current computer, just follow the steps below.

  • Clone this repository.
  • Run the MainTest class from your IDE (full example of a working application that integrates PayHook).
  • The first run will fail, but create a test-credentials.yml where you must enter your desired payment processors sandbox/test credentials (also create an ngrok account, which is needed to tunnel/forward the webhook events from a public domain/ip to your current computer).
  • Rerun the MainTest and enter help for a list of available commands once initialised.

Real-world usages

I am running PayHook on my own site at https://autoplug.one/store. Even though my site is closed-source you can see how I implemented PayHook below, so you can get a feel for how its done in a real-world application.

<details> <summary>Show/Hide code</summary>
/**
 * GlobalData
 */
public class GD {

    public static Product SERVER_AD_24h;
    public static Product SERVER_AD_72h;
    public static Product SERVER_AD_120h;

    public static Product PREMIUM_1M;
    public static Product PREMIUM_3M;
    public static Product PREMIUM_6M;
    public static Product PREMIUM_12M;

    public static Product SELF_HOST;
    public static Product SELF_HOST_1M;
    public static Product SELF_HOST_3M;
    public static Product SELF_HOST_6M;
    public static Product SELF_HOST_12M;

    static {

        try {
            PayHook.init(
                    "Osiris-Codes",
                    Config.master.databaseUrl,
                    Config.master.databaseName,
                    Config.master.databaseUser,
                    Config.master.databasePassword,
                    Application.IS_TEST_MODE, // Sandbox mode?
                    Config.master.linkWebsite + "/order-created",
                    Config.master.linkWebsite + "/order-aborted");

            String paypalHookUrl = Config.master.linkWebsite + "/paypal-hook";
            String stripeHookUrl = Config.master.linkWebsite + "/stripe-hook";

            //
            // Init processors
            //
            AL.info("paypalHookUrl: " + paypalHookUrl + " stripeHookUrl: " + stripeHookUrl);
            PayHook.initPayPal(Application.IS_TEST_MODE ? Config.master.paypalSandboxClientId : Config.master.paypalClientId,
                    Application.IS_TEST_MODE ? Config.master.paypalSandboxClientSecret : Config.master.paypalClientSecret,
                    paypalHookUrl);
            PayHook.initStripe(Application.IS_TEST_MODE ? Config.master.stripeSandboxSecret : Config.master.stripeSecretKey,
                    stripeHookUrl);

            //
            // Events for server advertisement
            //
            BetterConsumer<Payment> onAuthorized = payment -> {
                List<Featured_User_Servers> servers = Featured_User_Servers.wherePayment_id().is(payment.id).get();
                if (servers.isEmpty())
                    throw new Exception("No featured server found for payment: " + payment.toPrintString());
                Featured_User_Servers server = servers.get(0);
                server.isAuthorized = 1;
                Featured_User_Servers.update(server);
            };
            BetterConsumer<Payment> onRefunded = payment -> {
                Featured_User_Servers.wherePayment_id().is(payment.id).remove();
            };
            BetterConsumer<Payment> onCancelled = payment -> {
                if (!payment.isAuthorized()) return; // Do nothing if the payment was not authorized
                payment.timestampCancelled = System.currentTimeMillis();
                Payment.update(payment); // To make sure this doesn't get executed again through expired event.
                Featured_User_Servers.wherePayment_id().is(payment.id).remove();
            };
            BetterConsumer<Payment> onExpired = payment -> {
                PayHook.cancelPayment(payment);
            };
            SERVER_AD_24h = PayHook.putProduct(0, 999, "EUR", "Server feature 24h", "One time payment for adding your server for 24 hours to the 'featured servers' list.",
                    Payment.Interval.NONE).onPaymentAuthorized(onAuthorized, AL::warn).onPaymentRefunded(onRefunded, AL::warn).onPaymentCancelled(onCancelled, AL::warn).onPaymentExpired(onExpired, AL::warn);
            SERVER_AD_72h = PayHook.putProduct(1, 2997, "EUR", "Server feature 72h", "One time payment for adding your server for 72 hours to the 'featured servers' list.",
                    Payment.Interval.NONE).onPaymentAuthorized(onAuthorized, AL::warn).onPaymentRefunded(onRefunded, AL::warn).onPaymentCancelled(onCancelled, AL::warn).onPaymentExpired(onExpired, AL::warn);
            SERVER_AD_120h = PayHook.putProduct(2, 4995, "EUR", "Server feature 120h", "One time payment for adding your server for 120 hours to the 'featured servers' list.",
                    Payment.Interval.NONE).onPaymentAuthorized(onAuthorized, AL::warn).onPaymentRefunded(onRefunded, AL::warn).onPaymentCancelled(onCancelled, AL::warn).onPaymentExpired(onExpired, AL::warn);

            //
            // Events for premium subscription
            //
            onAuthorized = payment -> {
                Commands.upgradeUserToPremium(payment.userId);
            };
            onRefunded = payment -> {
                Commands.downgradePremiumUser(payment.userId);
            };
            onCancelled = payment -> {
                if (!payment.isAuthorized()) return; // Do nothing if the payment was not authorized
                // Only disable if there is no time left
                Subscription sub = new Subscription(payment);
                if (!sub.isTimeLeft()) {
                    Commands.downgradePremiumUser(payment.userId);
                    payment.timestampCancelled = System.currentTimeMillis();
                    Payment.update(payment); // To make sure this doesn't get executed again through expired event.
                }
            };

            // Currently, discounts are not added:
            PREMIUM_1M = PayHook.putProduct(3, 199, "EUR", "Premium-Membership", "Recurring payment for Premium-Membership at " + Config.master.linkRawWebsite + ".",
                    Payment.Interval.MONTHLY).onPaymentAuthorized(onAuthorized, AL::warn).onPaymentRef

Related Skills

View on GitHub
GitHub Stars29
CategoryData
Updated11mo ago
Forks3

Languages

Java

Security Score

87/100

Audited on Apr 9, 2025

No findings