NAME

WWW::PayPal - Perl client for the PayPal REST API

VERSION

version 0.002

SYNOPSIS

use WWW::PayPal;

my $pp = WWW::PayPal->new(
    client_id => $ENV{PAYPAL_CLIENT_ID},
    secret    => $ENV{PAYPAL_SECRET},
    sandbox   => 1,                    # default: 0 (live)
);

# Create an order — one-liner Express Checkout replacement
my $order = $pp->orders->checkout(
    amount     => '42.00',
    currency   => 'EUR',
    return_url => 'https://example.com/paypal/return',
    cancel_url => 'https://example.com/paypal/cancel',
    brand_name => 'My Shop',
);

# Redirect the buyer here:
my $approve_url = $order->approve_url;

# After the buyer approves, capture the payment
# (replaces GetExpressCheckoutDetails + DoExpressCheckoutPayment)
my $captured = $pp->orders->capture($order->id);

print $captured->payer_email, "\n";
print $captured->fee_in_cent, "\n";

# Refund a capture
$pp->payments->refund($captured->capture_id,
    amount => { currency_code => 'EUR', value => '10.00' });

# --- Recurring subscriptions ---

# One-time merchant setup: product + plan (do this at deploy time)
my $product = $pp->products->create(
    name => 'VIP membership', type => 'SERVICE', category => 'SOFTWARE',
);
my $plan = $pp->plans->create_monthly(
    product_id => $product->id,
    name       => 'VIP monthly',
    price      => '9.99',
    currency   => 'EUR',
);

# Per-user: create a subscription, redirect the buyer
my $sub = $pp->subscriptions->create(
    plan_id    => $plan->id,
    return_url => 'https://example.com/paypal/sub/return',
    cancel_url => 'https://example.com/paypal/sub/cancel',
);
my $approve_url = $sub->approve_url;

# Later: lifecycle
$sub->refresh;
$sub->suspend(reason => 'user paused');
$sub->activate(reason => 'resumed');
$sub->cancel(reason   => 'user quit');

DESCRIPTION

WWW::PayPal wraps PayPal's REST API. The initial release covers the Checkout / Orders v2 flow (one-off product sales, replacing the legacy NVP ExpressCheckout dance) and the Billing Subscriptions v1 flow (recurring monthly/yearly payments).

Operation dispatch uses cached OpenAPI operation tables (see WWW::PayPal::Role::OpenAPI), so no spec parsing happens at runtime.

client_id

PayPal REST app client ID. Defaults to the PAYPAL_CLIENT_ID environment variable.

secret

PayPal REST app secret. Defaults to the PAYPAL_SECRET environment variable.

sandbox

When true, all requests go to api-m.sandbox.paypal.com. Defaults to the PAYPAL_SANDBOX environment variable.

base_url

API base URL. Derived from "sandbox" by default.

orders

Returns a WWW::PayPal::API::Orders controller for the Checkout / Orders v2 API.

payments

Returns a WWW::PayPal::API::Payments controller for captures, refunds and authorizations.

products

Returns a WWW::PayPal::API::Products controller for catalog products (the abstract "what you're selling", referenced by plans).

plans

Returns a WWW::PayPal::API::Plans controller for billing plans (the recurring-cycle definitions that subscriptions reference).

subscriptions

Returns a WWW::PayPal::API::Subscriptions controller for creating and managing per-user recurring subscriptions.

js_sdk_url

my $url = $pp->js_sdk_url(
    currency => 'EUR',
    intent   => 'capture',           # or 'authorize' / 'subscription'
    # optional:
    locale          => 'de_DE',
    components      => ['buttons'],  # or 'buttons,funding-eligibility'
    disable_funding => ['credit', 'card'],
    vault           => 1,            # required for subscriptions
    merchant_id     => '...',        # multi-seller / partner flows
    buyer_country   => 'DE',         # sandbox only
    debug           => 1,
);

Builds the PayPal JS SDK script URL with the given parameters, using this client's client_id. For subscriptions, pass intent => 'subscription' and vault => 1.

js_sdk_script_tag

my $html = $pp->js_sdk_script_tag(currency => 'EUR');
# -> <script src="https://www.paypal.com/sdk/js?client-id=...&amp;currency=EUR&amp;..."></script>

Returns a ready-to-render HTML <script> tag for the JS SDK, HTML- escaped. Use this in your Catalyst / Mojolicious / whatever template to drop the SDK onto the page. All arguments are forwarded to "js_sdk_url".

Typical integration (server-side order creation, client-side approval):

# in your template
[% pp.js_sdk_script_tag(currency => 'EUR') %]
<div id="paypal-button"></div>
<script>
paypal.Buttons({
  createOrder: function() {
    return fetch('/paypal/create', {method:'POST'})
      .then(function(r){ return r.json() })
      .then(function(d){ return d.id });
  },
  onApprove: function(data) {
    return fetch('/paypal/capture/' + data.orderID, {method:'POST'})
      .then(function(r){ return r.json() })
      .then(function(d){ window.location = '/thanks?o=' + d.id });
  }
}).render('#paypal-button');
</script>

# in your controller:
# POST /paypal/create  -> $pp->orders->checkout(...); return {id => $o->id}
# POST /paypal/capture/:id -> $pp->orders->capture($id); return {id => ...}

SEE ALSO

SUPPORT

Issues

Please report bugs and feature requests on GitHub at https://github.com/Getty/p5-www-paypal/issues.

CONTRIBUTING

Contributions are welcome! Please fork the repository and submit a pull request.

AUTHOR

Torsten Raudssus <torsten@raudssus.de> https://raudssus.de/

COPYRIGHT AND LICENSE

This software is copyright (c) 2026 by Torsten Raudssus.

This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.