Launched 'hoozyu' - our new Meteor app that helps young people to understand and explain themselves

Thought I’d share our new site. I always find it interesting to see what people are doing with Meteor so hopefully some of you will too. It isn’t a particularly complex use of Meteor but it’s in production which has to count for something right!?

Would love to hear any feedback or questions from a technical or non-technical point of view, and I’ll add to the post if anyone wants more info…

https://hoozyu.com

What is hoozyu?:

What hoozyu does for you, is to teach you to explain yourself. If that’s a surprising idea, it is probably because most adults can’t really explain themselves either. If you can explain yourself – who you are, what matters to you, and what fuels your passion and engagement – then you have the edge in planning how you are going to invest your life and energy.

Tech / services used:

Meteor, MongoDB (Compose.io), Coffee Script, PM2, Iron Router (for now), Kadira, PayPal Adaptive Payments, Mandrill, MailChimp, Papertrail (Logging), and a few other APIs for different bits and pieces, and lots of stuff I’ve probably forgotten about…

Hosting:

It seems most people working with Meteor are using MUP (usually with Digital Ocean), Modulus or Galaxy. We’re actually currently hosted on Linode (referral link) because they are pretty awesome! We’re using SaltStack to configure our servers and manage deployment to various environments. When we start a deploy, SaltStack just runs commands to build a particular commit of our app with meteor build on the server then run it with PM2. We’re running nginx in front of each meteor instance to serve everything (css,js,images) except the initial html bootstrap which I don’t think is possible to deliver without meteor. All instances are then load balanced behind haproxy which also does the TLS termination.

3 Likes

What package did you use for PayPal adaptive payments? Working on a project for bus reservations system and I would like for the users to be able to collect money. Seem like PayPale adaptive payments might be a good fit.

I’m not sure if you can still register new Adaptive Payments apps, ours were registered back in 2013. The API is very old and PayPal “support” never seem to know anything about it. I would not recommend using it, we do because it’s our only option to support chained payments between pretty much any country, and even then it’s somewhat clunky for receivers to have to set up. Stripe and Braintree have more modern solutions. I’m not sure where you’re based but they are US-only so we can’t use them.


However, if you are using Adaptive Payments, read on…

I didn’t use any packages and just wrote my own - the API is fairly straightforward (as far as PayPal APIs go…). I can’t share it in full but I’ve stripped out the basics below. You also need an IPN processor (basically a server side route that processes the incoming IPN messages from PayPal). We used meteorhacks:picker and body-parser with urlencoded parser to do server-side routing / parsing PayPal’s messages.

The IPN processing is somewhat complex because there are two types of IPN and if they arrive in the wrong order you have to save the first one and process it later. This is because there’s no way to link the normal PayPal IPN to an order in the database until you receive the Adaptive Payments IPN (which has all the transaction IDs and can therefore be linked). With the Adaptive Payments IPN the status can be ‘COMPLETED’ but the sub-transactions be ‘PENDING’ so it’s important to check all sub-transactions. Most common ‘PENDING’ reason would be currency not supported by the secondary receiver’s account.

Here’s the code for generating a new payment request (a PayKey). Requires Big.js because floating point math isn’t suitable for currency calculations.

    #####
    # Create a PayKey for a single or chained payment
    #
    # Chain format for a $50 payment: 
    #   [
    #       {
    #           amount: 3000, 
    #           primary: true, 
    #           email: 'your-paypal@account.com'
    #       }, 
    #       {
    #           amount: 2000, 
    #           primary: false, 
    #           email: 'their-paypal@account.com'
    #       }
    #   ]
    #####
    createPaymentRequest: (options) ->
        { chain, total, paypalMode } = options

        # Set primary receiver amount to the total amount (other amounts are deducted from this total by PayPal)
        chain = _(chain).map (receiver) ->
            receiver.amount = new Big(receiver.amount).div(100).toFixed(2)
            if receiver.primary
                receiver.amount = new Big(total).div(100).toFixed(2) # For our $50 payment, receiver[0].amount is now 50.00
            receiver

        # Remove primary flag if there is only one receiver, PayPal doesn't like it...
        if chain.length is 1
            delete chain[0].primary

        request =
            headers:
                'X-PAYPAL-SECURITY-USERID':         'Your PayPal API User'
                'X-PAYPAL-SECURITY-PASSWORD':       'Your PayPal API Password'
                'X-PAYPAL-SECURITY-SIGNATURE':      'Your PayPal API Signature'
                'X-PAYPAL-APPLICATION-ID':          'Your PayPal API Application ID'
                'X-PAYPAL-REQUEST-DATA-FORMAT':     'JSON'
                'X-PAYPAL-RESPONSE-DATA-FORMAT':    'JSON'
            data:
                actionType:         'PAY'
                cancelUrl:          'https://your-site.com/some/route'
                returnUrl:          'https://your-site.com/another/route'
                ipnNotificationUrl: 'https://your-site.com/ipn/route'
                currencyCode:       'USD'
                requestEnvelope:
                    errorLanguage:  'en_GB'
                feesPayer:          'EACHRECEIVER'
                trackingId:         'My Tracking'
                #fundingConstraint:  'CREDITCARD,BALANCE' # PayPal won't activate this unless you are a financial institution, but there is a setting on the main PayPal website that allows you to reject E-CHECK payments (they take a week to clear so not good for instant online transactions)...
                receiverList:
                    receiver: chain

        if paypalMode is 'SANDBOX'
            request.headers['X-PAYPAL-SANDBOX-EMAIL-ADDRESS'] = 'Your sandbox email acccount'

        response = HTTP.call 'POST', 'https://svcs.sandbox.paypal.com/AdaptivePayments/Pay', request