Paypal support in Grav

Hi all,
is there any way to implement paypal? I’ve searched the forum but only found old posts, mainly about https://gravcart.com/ which is not maintained anymore. Unfortunately that’s the only plugin I found for “Paypal”. I also searched for “Shop” and found plugins for Stripe and Snipcart (also some years ago, not sure whether they are still working) but nothing for Paypal.
I want to build a site where users could login, pay a small fee via paypal to disabled ads and add some extra features.
Is that somehow possible with Grav?
Many thanks

try codeshack shopping cart in google, or snipcart

Thanks @lisekbox ! I’ve checked snipcart before but on the Grav Demo it only has Visa, Master and AmEx, no paypal. But it has Paypal on their supported gateways so that’s nice.
Codeshack also has Paypal, but needs a MySQL DB which should not be a problem. But it has no Grav Plugin. Not sure whether this might be a problem as I want to sell online subscriptions so Codeshack / Snipcart / Paypal need to “talk” to Grav to supply the information about the paid subscription.
Do you have any experience with one of these products on your own site?

It looks like you will have to create own custom plugin to fit all your needs…

I didn’t try them yet, but after new year I will test codeshack shopping cart for restaurant orders.

However I did try commenting system. It is not working out of the box - it was pain in my hole for a week time, but after some code modifications I did integration with website and admin panel. It is working great!

Thanks for the update! I’m not sure whether I will be able to create my own plugin as I’m more with .net (c#, ms sql etc) and of course html & co but did not take care about php yet. But it sounds interesting, maybe i’ll have a look. Please keep me also informed about your tests and progress! Many thanks!

PayPal should be simple. When you create a payment button on the PayPal site it gives you the html code to use. Just paste it into the bottom of your …md file and it will work.

Here’s an example (I have frontend code that actually dynamically generates the PayPal code depending upon what the person is buying).

<table width=“635”>
<tr>
<td colspan=“2” align=“left”><h4>Test Payment: $1.00<h4></td><td valign=“top” align=“left” colspan=“2”> <form action=“https://www.paypal.com/cgi-bin/webscr” method=“post”>
<input type=“hidden” name=“cmd” value=“_xclick”>
<input type=“hidden” name=“business” value=“PPLABCDEFGH12345”>
<input type=“hidden” name=“lc” value=“US”>
<input type=“hidden” name=“item_name” value=“Extra Large WhatChaMaCallet”>
<input type=“hidden” name=“button_subtype” value=“services”>
<input type=“hidden” name=“cn” value=“Add special instructions to the XXX”>
<input type=“hidden” name=“no_shipping” value=“2”>
<input type=“hidden” name=“currency_code” value=“USD”>
<input type=“hidden” name=“bn” value=“PP-BuyNowBF:btn_paynowCC_LG.gif:NonHosted”>
<input type=“hidden” name=“on0” value=“Item Purchase”>
<input type=“hidden” name=“os0” value=“Extra Large WhatChaMaCallet”>
<input type=“hidden” name=“amount” value=“1.0”>
<input type=“hidden” name=“invoice” value=002469>
<input type=“hidden” name=“custom” value=index.php?option=com_content&task=view&id=63&Itemid=47><input type=“hidden” name=“return” value=“—my domain here—”>
<input type=“hidden” name=“cancel_return” value=“—my domain here—”>
<input type=“image” src=“https://www.paypal.com/en_US/i/btn/btn_paynowCC_LG.gif” border=“0” name=“submit” alt=“PayPal - The safer, easier way to pay online!”><img alt=“” border=“0” src=“…paypal url removed so forum would allow post…” width=“1” height=“1”></td></tr>
</table></center></form>

1 Like

And now, with Grav I’m working on modifying the shop/snipcart to do PayPal with twig and …md files.`

Example of what I’ve got so far:

Modified snipcart.yaml to add after the api_key line:
paypal_entrypted: -----BEGIN PKCS7-----MIIG1QYJKoZ…long string supplied by PayPal…

Modified snipcart_category.html.twig:

 if (child.header.paypalitem is not defined)
   if (child.header.unpublished is not defined or (child.header.unpublished == false))
     include ‘partials/snipcart_product_item.html.twig’ with {‘page’: child, ‘parent’: page}
   endif
 else
   include ‘partials/paypal_product_item.html.twig’ with {‘page’: child, ‘parent’: page}
 endif

Modified snipcart_product_item.html.twig into paypal_product_item.html.twig
(partial extract, but you get the idea):

{% set snipcart_image = page.media.images|first %}
<div class="snipcart-item block" >
    {{ page.order|trim('.') }}
    <h3 style:"margin-bottom:0px;"><a href="{{ page.url }}">{{ page.header.title }}</a></h3>
    <div class="snipcart-thumb">
        {% if snipcart_image %}
            <a href="{{ page.url }}">{{ snipcart_image.cropResize(200,200).html('page.header.title','snipcart-thumb-image')|raw }}</a>
        {% endif %}
        <span class="snipcart-price">
        {% if page.header.price %}
        ${{ page.header.price }}
        {% else %}
        $ varies
        {% endif %}
        </span>
    </div>
    <div class="ppsnipcart-details" style="paddling-left: 5px">
        {{ page.header.blurb }}
        <br>
        {{ page.header.blurb2 }}
        <br>
<div style="display:table; padding:10px 0px 0px 0px; margin:0px; font-size: 12px; padding-top:5px; line-height:0; height:20px; min-height:0px;">
  <div style="display: table-row; padding:0px 0px 0px 0px; margin-top: 0px; margin-bottom:0px; padding-top:0px; line-height:0; height:0px; min-height:0px;">
    <div style="width:10%; padding:0px 0px 0px 0px; display:table-cell; padding-top:0px; line-height:20px; height:0px; min-height:0px;">
      <form target="paypal" action="https://www.paypal.com/cgi-bin/webscr" method="post" style="height:20px;">
      <input type="hidden" name="cmd" value="_s-xclick">
      <input type="hidden" name="hosted_button_id" value="{{ page.header.title }}">
      <input type="hidden" name="on0" value="{{ page.header.option0_name }}">
      &nbsp;
    </div>
    <div style="width: 20%; padding:0px 0px 0px 0px; display: table-cell; line-height:0; height:0px; min-height:0px;">
      {{ page.header.option0_name }}:
    </div>
    <div style="width: 40%; padding:0px 0px 0px 0px; display: table-cell; line-height:0; height:0px; min-height:0px;">
      <select name="os0" style="height: 20px; white-space: pre-wrap;">
      <option value="{{ page.header.option0_sv0 }}">{{ page.header.option0_st0 }}</option>
      {% if page.header.option0_sv1 %} <option value="{{ page.header.option0_sv1 }}">{{ page.header.option0_st1 }}</option> {% endif %}
      ...
      {% if page.header.option0_sv9 %} <option value="{{ page.header.option0_sv9 }}">{{ page.header.option0_st9 }}</option> {% endif %}
      </select>
    </div>
    <div style="width: 30%; padding:0px 0px 0px 0px; display: table-cell; line-height:0; height:0px; min-height:0px;">&nbsp;</div>
  </div>
  {% if page.header.option1_name %}
  <div style="display: table-row; padding:0px 0px 0px 0px; margin-top:0px; margin-bottom:0px; line-height:0; height:0px; min-height:20px;">
    <div style="width: 10%; display: table-cell; padding:0px 0px 0px 0px; line-height:0; height:20px;">&nbsp;</div>
    <div style="width: 20%; display: table-cell; padding:0px 0px 0px 0px; line-height:0; height:20px;">
      {{ page.header.option1_name }}:
    </div>
    <div style="width: 40%; display: table-cell; padding:0px 0px 0px 0px; line-height:0; height:20px;">
      <input type="hidden" name="on1" value="{{ page.header.option1_name }}">
      <select name="os1" style="height: 20px; white-space: pre-wrap;">
      <option value="{{ page.header.option1_sv0 }}">{{ page.header.option1_st0 }}</option>
      {% if page.header.option1_sv1 %} <option value="{{ page.header.option1_sv1 }}">{{ page.header.option1_st1 }}</option> {% endif %}
      ...
      {% if page.header.option1_sv9 %} <option value="{{ page.header.option1_sv9 }}">{{ page.header.option1_st9 }}</option> {% endif %}
      </select>
    </div>
    <div style="width: 30%; display: table-cell; padding:0px 0px 0px 0px; line-height:0; height:20px;">&nbsp;</div>
  </div>
  {% endif %}
  {% if page.header.option2_name %}
  <div style="display: table-row; padding:0px 0px 0px 0px; margin-top:0px; margin-bottom:0px; line-height:0; height:20px;">
    <div style="width: 10%; display: table-cell; padding:0px 0px 0px 0px; line-height:0; height:20px;">&nbsp;</div>
    <div style="width: 20%; display: table-cell; padding:0px 0px 0px 0px; line-height:0; height:20px;">
      {{ page.header.option2_name }}:
    </div>
    <div style="width: 40%; display: table-cell; padding:0px 0px 0px 0px; line-height:0; height:20px;">
      <input type="hidden" name="on2" value="{{ page.header.option2_name }}">
      <select name="os2" style="height: 20px; white-space: pre-wrap;">
      <option value="{{ page.header.option2_sv0 }}">{{ page.header.option2_st0 }}</option>
      {% if page.header.option2_sv1 %} <option value="{{ page.header.option2_sv1 }}">{{ page.header.option2_st1 }}</option> {% endif %}
      ...
      {% if page.header.option2_sv9 %} <option value="{{ page.header.option2_sv9 }}">{{ page.header.option2_st9 }}</option> {% endif %}
      </select>
    </div>
    <div style="width: 30%; display: table-cell; padding:0px 0px 0px 0px; line-height:0; height:20px;">&nbsp;</div>
  </div>
  {% endif %}
  <div style="display: table-row; margin-top:0px; margin-bottom:0px;">
    <div style="width: 10%; padding:0px 0px 0px 0px; display: table-cell;">&nbsp;</div>
    <div style="width: 20%; padding:0px 0px 0px 0px; display: table-cell;">
     <img src="/images/PayPalLogoTrim.jpg" style="width:40px; text-align:top; float:right;">
    </div>
    <div style="display:table; padding:10px 0px 0px 0px; margin:0px; font-size: 12px; line-height:0; height:20px;">
      <div style="display: table-row; margin-top:0px; margin-bottom:0px;">
      <div style="display: table-cell; padding:0px 0px 0px 0px;">
      <input type="hidden" name="currency_code" value="USD">
      <input type="image" src="/images/PayPal_Add_btn_cart_LG.gif" border="0" name="submit" alt="PayPal - The safer, easier way to pay online!">
      </form>
    </div>
    <div style="display: table-cell; padding:0px 0px 0px 0px;">&nbsp;</div>
    <div style="display: table-cell; padding:0px 0px 0px 0px;">
      <form target="paypal" action="https://www.paypal.com/cgi-bin/webscr" method="post" >
      <input type="hidden" name="cmd" value="_s-xclick">
      <input type="image" src="/images/PayPal_View_btn_viewcart_LG.gif" name="submit" alt="PayPal - The safer, easier way to pay online!">
      <input type="hidden" name="encrypted" value="{{ header.snipcart.paypal_encrypted }}">
      </form>
    </div>
  </div>
</div>
1 Like

And here’s the MD header. I also added text & html in the raw data to display extra info if the user clicks on the page instead of the PayPal link. Also, all the values below must match the values used when the PayPal Button was created.

(Note, this is a work in progress. Ultimately I want to create a separate plugin. Contact me offline if you want to help/debug/etc.)

title: Womens’ Moisture-Wicking TShirts
menu: Top
unpublished: false
blurb: ‘(Click image to see all shirts’
blurb2: ‘and availability for each style)’
image: LPTT2-LadiesTankPoly.png
paypalitem: true
hosted_button_id: XP123456789EE
option0_name: Size
option0_sv0: Small
option0_st0: Small $16.50 USD
option0_sv1: Medium
option0_st1: Medium $16.50 USD
option0_sv2: Large
option0_st2: Large $16.50 USD
option0_sv3: X-Large
option0_st3: X-Large $16.50 USD
option0_sv4: 2X
option0_st4: 2X $17.50 USD
option0_sv5: 3X
option0_st5: 3X $18.50 USD
option0_sv6: 4X
option0_st6: 4X $19.50 USD
option1_name: Color
option1_sv0: Green
option1_st0: Green
option2_name: Style
option2_sv0: LPSSS
option2_st0: Short Sleeve Polyester
option2_sv1: LPSSS2
option2_st1: Short Sleeve Scoop Neck Polyester
option2_sv2: LPSLV
option2_st2: Sleeveless V-Neck Polyester
option2_sv3: LPLSV
option2_st3: Long Sleeve V-Neck Polyester
option2_sv4: LPSSV2
option2_st4: Short Sleeve V-Neck Polyester (Kelly)
option2_sv5: LPSSV
option2_st5: Short Sleeve V-Neck Polyester (Lime)
option2_sv6: LPTT
option2_st6: Tank Top Scoop Neck Polyester (Kelly)
option2_sv7: LPTT2
option2_st7: Tank Top Scoop Neck Polyester (Lime)

2 Likes

It’s working :slightly_smiling_face: - need to fetch() Grav header and footer to codeshack, and fetch() from there basket icon (to show number of items in basket when user is browsing Grav pages)