I have implemented PayPal checkout API in my rails application by using the SmartButtons and by creating the order in the server-side.
I have used the payouts-ruby-sdk gem and my code is as follows:-
index.html.erb
<!-- Set up a container element for the button -->
<div id="paypal-button-container"></div>
<!-- Include the PayPal JavaScript SDK -->
<script src="https://www.paypal.com/sdk/js?client-id=xyz¤cy=USD"></script>
<script>
// Render the PayPal button into #paypal-button-container
paypal.Buttons({
// Call your server to set up the transaction
createOrder: function(data, actions) {
return fetch('/orders', {
method: 'post'
}).then(function(res) {
return res.json();
}).then(function(orderData) {
return orderData.orderID;
});
},
// Call your server to finalize the transaction
onApprove: function(data, actions) {
return fetch('/orders/' + data.orderID + '/capture', {
method: 'post'
}).then(function(res) {
return res.json();
}).then(function(orderData) {
// Three cases to handle:
// (1) Recoverable INSTRUMENT_DECLINED -> call actions.restart()
// (2) Other non-recoverable errors -> Show a failure message
// (3) Successful transaction -> Show a success / thank you message
// Your server defines the structure of 'orderData', which may differ
var errorDetail = Array.isArray(orderData.details) && orderData.details[0];
if (errorDetail && errorDetail.issue === 'INSTRUMENT_DECLINED') {
// Recoverable state, see: "Handle Funding Failures"
// https://developer.paypal.com/docs/checkout/integration-features/funding-failure/
return actions.restart();
}
if (errorDetail) {
var msg = 'Sorry, your transaction could not be processed.';
if (errorDetail.description) msg += '\n\n' + errorDetail.description;
if (orderData.debug_id) msg += ' (' + orderData.debug_id + ')';
// Show a failure message
return alert(msg);
}
// Show a success message to the buyer
alert('Transaction completed');
});
}
}).render('#paypal-button-container');
</script>
orders_controller.rb
class OrdersController < ApplicationController
skip_before_action :verify_authenticity_token
def index
end
def create
# Creating Access Token for Sandbox
client_id = 'xyz'
client_secret = 'abc'
# Creating an environment
environment = PayPal::SandboxEnvironment.new(client_id, client_secret)
client = PayPal::PayPalHttpClient.new(environment)
request = PayPalCheckoutSdk::Orders::OrdersCreateRequest::new
request.request_body({
intent: "CAPTURE",
purchase_units: [
{
amount: {
currency_code: "USD",
value: "10.00"
}
}
]
})
begin
# Call API with your client and get a response for your call
# debugger
response = client.execute(request)
puts response.result.id
render json: {success: true, orderID: response.result.id}
rescue PayPalHttp::HttpError => ioe
# Something went wrong server-side
puts ioe.status_code
puts ioe.headers["debug_id"]
end
end
def execute_payment
client_id = 'xyz'
client_secret = 'abc'
# Creating an environment
environment = PayPal::SandboxEnvironment.new(client_id, client_secret)
client = PayPal::PayPalHttpClient.new(environment)
request = PayPalCheckoutSdk::Orders::OrdersCaptureRequest::new(session[:orderID])
begin
# Call API with your client and get a response for your call
response = client.execute(request)
# If call returns body in response, you can get the deserialized version from the result attribute of the response
order = response.result
puts order
rescue PayPalHttp::HttpError => ioe
# Something went wrong server-side
puts ioe.status_code
puts ioe.headers["debug_id"]
end
end
end
Now I want to implement the Paypal's Payouts API and I know that paypal-ruby-sdk is available for it but I am confused where to fit this code and how to integrate it with the front end. Any ideas? Thanks in advance :)
Your code above is Checkout code, for both front-end (JavaScript), and back-end (Ruby).
Payouts has nothing to do with Checkout, neither front-end Checkout nor back-end Checkout.
Payouts is strictly a backend API operation, where you send money from your account to another account.
Payouts does not connect to any front-end UI. You can build your own UI to trigger a payout, if you need one. Presumably you know who you want to send money from your account to, and what process should trigger this action.
Related
I'm using twitter account activity api for registering a webhook using autohook wrapper library.
As per the docs the post requests to endpoint (https://api.twitter.com/1.1/account_activity/all/:env_name/webhooks.json) should trigger a get request from my server. that will return a crc based on a crc token and my consumer key.
While im getting code 215 and unable to connect during crc request error message again and again.
Im not sure why get request is not exceuting. here is my server and client code.
server.py
#app.route("/webhook/twitter", methods=["GET", "POST"])
def callback() -> json:
if flask.request.method == "GET" or flask.request.method == "PUT":
print(flask.request.args.get("crc_token"))
hash_digest = hmac.digest(
key=os.environ["consumer_secret"].encode("utf-8"),
msg=flask.request.args.get("crc_token").encode("utf-8"),
digest=hashlib.sha256,
)
return {
"response_token": "sha256="
+ base64.b64encode(hash_digest).decode("ascii")
}
elif flask.request.method == "POST":
data = flask.request.get_json()
logging.info(data)
return {"code": 200}
# Once the code running on the server.
# You can register and subscribe to events from your local machine.
#app.route("/",methods=["GET"])
def callbackget():
return "in GEt"
if __name__ == "__main__":
app.run(debug=True, port=443,ssl_context=context)
client side autohook wrapper:
const { Autohook } = require('twitter-autohook');
(async ƛ => {
const webhook = new Autohook(
{
token:"1218220064610099203-8CnN6G3edmIRGdDpLcR7Bao88mMCUg",
token_secret:"JEavuf3PZmSsXWhmSdtNarFPNBp6BsZzMrLpkEMASkJX5",
consumer_key:"6n7U6KDXxouXDcsLMxdiSKwZ9",
consumer_secret:"o2aKE9TivbTwchJXXZ4XaTyAwtCgtoRO35a9PgdaqMLUBRfv49",
env:"enc"
}
);
// Removes existing webhooks
await webhook.removeWebhooks();
// Listens to incoming activity
webhook.on('event', event => console.log('Something happened:', event));
// Starts a server and adds a new webhook
await webhook.start("https://127.0.0.1:443/webhook/twitter");
// Subscribes to a user's activity
await webhook.subscribe({oauth_token, oauth_token_secret});
})();
I've read here that it's possible to send an IPN directly to a Google cloud function. I have my Google Cloud functions running on Firebase on an index.js file.
I've set up my Paypal buttons to send the IPN to a page on my webapp.
Here is an example of one of the functions I'm running off Google Cloud Functions/Firebase:
// UPDATE ROOMS INS/OUTS
exports.updateRoomIns = functions.database.ref('/doors/{MACaddress}').onWrite((change, context) => {
const beforeData = change.before.val();
const afterData = change.after.val();
const roomPushKey = afterData.inRoom;
const insbefore = beforeData.ins;
const insafter = afterData.ins;
if ((insbefore === null || insbefore === undefined) && (insafter === null || insafter === undefined) || insbefore === insafter) {
return 0;
} else {
const updates = {};
Object.keys(insafter).forEach(key => {
updates['/rooms/' + roomPushKey + '/ins/' + key] = true;
});
return admin.database().ref().update(updates); // do the update}
}
return 0;
});
Now question:
1) I want to add another function to process IPN from Paypal as soon as I have a transaction. How would I go about this?
I'll mark the answer as correct if solves this first question.
2) how would that Google cloud function even look like?
I'll create another question if you can solve this one.
Note I am using Firebase (no other databases nor PHP).
IPN is simply a server that tries to reach a given endpoint.
First, you have to make sure that your firebase plan supports 3rd party requests (it's unavailable in the free plan).
After that, you need to make an http endpoint, like so:
exports.ipn = functions.http.onRequest((req, res) => {
// req and res are instances of req and res of Express.js
// You can validate the request and update your database accordingly.
});
It will be available in https://www.YOUR-FIREBASE-DOMAIN.com/ipn
Based on #Eliya Cohen answer:
on your firebase functions create a function such as:
exports.ipn = functions.https.onRequest((req, res) => {
var reqBody = req.body;
console.log(reqBody);
// do something else with the req.body i.e: updating a firebase node with some of that info
res.sendStatus(200);
});
When you deploy your functions go to your firebase console project and check your functions. You should have something like this:
Copy that url, go to paypal, edit the button that's triggering the purchase, scroll down to Step 3 and at the bottom type:
notify_url= paste that url here
Save changes.
You can now test your button and check the req.body on your firebase cloud functions Log tab.
Thanks to the answers here, and especially to this gist: https://gist.github.com/dsternlicht/fdef0c57f2f2561f2c6c477f81fa348e,
.. finally worked out a solution to verify the IPN request in a cloud func:
let CONFIRM_URL_SANDBOX = 'https://ipnpb.sandbox.paypal.com/cgi-bin/webscr';
exports.ipn = functions.https.onRequest((req, res) => {
let body = req.body;
logr.debug('body: ' + StringUtil.toStr(body));
let postreq = 'cmd=_notify-validate';
// Iterate the original request payload object
// and prepend its keys and values to the post string
Object.keys(body).map((key) => {
postreq = `${postreq}&${key}=${body[key]}`;
return key;
});
let request = require('request');
let options = {
method: 'POST',
uri : CONFIRM_URL_SANDBOX,
headers: {
'Content-Length': postreq.length,
},
encoding: 'utf-8',
body: postreq
};
res.sendStatus(200);
return new Promise((resolve, reject) => {
// Make a post request to PayPal
return request(options, (error, response, resBody) => {
if (error || response.statusCode !== 200) {
reject(new Error(error));
return;
}
let bodyResult = resBody.substring(0, 8);
logr.debug('bodyResult: ' + bodyResult);
// Validate the response from PayPal and resolve / reject the promise.
if (resBody.substring(0, 8) === 'VERIFIED') {
return resolve(true);
} else if (resBody.substring(0, 7) === 'INVALID') {
return reject(new Error('IPN Message is invalid.'));
} else {
return reject(new Error('Unexpected response body.'));
}
});
});
});
Also thanks to:
https://developer.paypal.com/docs/classic/ipn/ht-ipn/#do-it
IPN listener request-response flow: https://developer.paypal.com/docs/classic/ipn/integration-guide/IPNImplementation/
To receive IPN message data from PayPal, your listener must follow this request-response flow:
Your listener listens for the HTTPS POST IPN messages that PayPal sends with each event.
After receiving the IPN message from PayPal, your listener returns an empty HTTP 200 response to PayPal. Otherwise, PayPal resends the IPN message.
Your listener sends the complete message back to PayPal using HTTPS POST.
Prefix the returned message with the cmd=_notify-validate variable, but do not change the message fields, the order of the fields, or the character encoding from the original message.
Extremely late to the party but for anyone still looking for this, PayPal have made a sample in their JS folder on their IPN samples Github repo.
You can find this at:
https://github.com/paypal/ipn-code-samples/blob/master/javascript/googlecloudfunctions.js
With ActionCable, how can I respond with an error after receiving data from a client?
For example, when the client fails to authenticate, ActionCable throws UnauthorizedError which responds with a 404. I want to respond with a 422, for example, when the data that the client sent is invalid.
ActionCable.server.broadcast "your_channel", message: {data: data, code: 422}
Then in your.coffee file:
received: (res) ->
switch res.code
when 200
# your success code
# use res.data to access your data message
when 422
# your error handler
From what I could gather there's no "Rails way" to do this, the answer given by #Viktor seems correct. In summary: ensure all messages are broadcasted with both data and code, then switch by code in the client.
For a more modern and ES6 example see below:
In rails:
require 'json/add/exception'
def do_work
// Do work or raise
CampaignChannel.broadcast_to(#campaign, data: #campaign.as_json, code: 200)
rescue StandardError => e
CampaignChannel.broadcast_to(#campaign, data: e.to_json, code: 422) # Actually transmitting the entire stacktrace is a bad idea!
end
In ES6:
campaignChannel = this.cable.subscriptions.create({ channel: "CampaignChannel", id: campaignId }, {
received: (response) => {
const { code, data } = response
switch (code) {
case 200:
console.log(data)
break
default:
console.error(data)
}
}
})
I am using grails-jaxrs for exposing an api which accepts multipart/form-data..
#POST
#Consumes(MediaType.MULTIPART_FORM_DATA)
#Produces(['application/json'])
#Path('/addMessage')
ChatMessage addChatMessageWithAttachment(#Context HttpServletRequest req) {
log.debug "> addChatMessageWithAttachment"
def fileStores = []
def chatMessage
GrailsWebRequest request = WebUtils.retrieveGrailsWebRequest()
def params = request.getParams()
if (!(params.messageBody.length() > 0)) {
log.error("Empty message body")
throw new LocalisedException(ErrorCode.CHAT_MESSAGE_CREATE_FAILED)
}
}
The implementation is working properly as expected. I am able to send form-data (with file and other parameters successfully.
Now I am trying to implement integration test for above logic.
I am using IntegrationTestCase to achieve this.. so my code snippet is as below:
class ChatMessageResourceV1Tests extends IntegrationTestCase{
//other implementation and setup ommited
#Test
void "Create new chat message for event id - customer user"() {
def headers = ['Content-Type': 'multipart/form-data', 'Accept': 'application/json']
def data = "My message..."
def cm = new ChatMessage(messageBody: data)
def content = "{'eventId':'$event.id','messageBody':'My message...'}"
sendRequest("/api/v1/chatMessage/addMessage", 'POST', headers, content.bytes)
assertEquals(200, response.status)
}
}
When I run the test.. I can see the call reaches inside the API method.. But however, the parameter messageBody is coming as null and exception is being thrown.
I have tried every possible combination of test.. But no luck.
Any help would be appreciated.
I am trying to make concurrent asynchronous HTTP calls using EventMachine::Iterator. I see too many calls ended up in errcallback. Is there any way to retry them? I see all these have deferred_status failed. How to avoid getting into that failed state.
urls = ["http://www.google.com"]*1000 #using this as an example
EventMachine.run do
EM::Iterator.new(urls, 1000).map(proc { |url, iter|
res = EventMachine::HttpRequest.new(url).get
sleep(0.100)
res.callback {
puts "#{res.req.uri} #{res.response_header.status}"
response =(JSON.parse(res.response)['response']) if res.response_header.status == 200
iter.return(response)
}
res.errback{
puts "Err => #{res.req.uri} #{res.response_header.status}"
iter.return(res.response_header.status)}
}, proc { |responses|
all = responses.flatten
puts 'all done!'
EM.stop
})
end