Grails and PayPal (Express Checkout)
There are two PayPal plugins for Grails, the first I found is a year old and seems to be in well enough order. The second it a bit older and apparently not compatible with 1.1+.
If I decide to not use an existing plugin what is the best way to implement a simple Express Checkout flow for Grails? I'm not entirely sure what goes into working with the PayPal开发者_运维技巧 API, and their documents are quite the mess at the moment with the move to X.com.
Here is the IPNcontroller I revealed from the Paypal plugin to match Paypal IPN requirement. It is in production.
package com.risguru.plugin.ipn
import com.risguru.plugin.shoppingcart.IOrderService
class PaymentController {
IOrderService orderService
def config = grailsApplication.config.grails.paypal
static allowedMethods = [buy: 'POST', notify: 'POST']
static defaultAction = 'index'
def index = {
redirect(controller:'company', action:'index')
}
def notify = {
log.debug "Received IPN notification from PayPal Server ${params}"
try {
def config = grailsApplication.config.com.risguru.plugin.ipn
def server = config.server
def receiver = params.email ?: config.receiver
if (!server || !receiver) throw new IllegalStateException("Paypal misconfigured! You need to specify the Paypal server URL and/or account email. Refer to documentation.")
params.cmd = "_notify-validate"
def queryString = params.toQueryString()[1..-1]
log.debug "Sending back query $queryString to PayPal server $server"
def url = new URL(server)
def conn = url.openConnection()
conn.doOutput = true
def writer = new OutputStreamWriter(conn.getOutputStream())
writer.write queryString
writer.flush()
def result = conn.inputStream.text?.trim()
log.debug "Got response from PayPal IPN $result"
def purchaseOrder = orderService.getOrderByTransactionID(params.transactionId)
if (purchaseOrder && result == 'VERIFIED') {
if (params.receiver_email != receiver) {
log.warn """WARNING: receiver_email parameter received from PayPal does not match configured e-mail. This request is possibly fraudulent!
REQUEST INFO: ${params}
"""
}
else {
request.purchaseOrder = purchaseOrder
def status = params.payment_status
if (purchaseOrder.paymentStatus != PaymentStatus.COMPLETE && purchaseOrder.paymentStatus != PaymentStatus.CANCELLED) {
if (purchaseOrder.paypalTransactionId && purchaseOrder.paypalTransactionId == params.txn_id) {
log.warn """WARNING: Request tried to re-use and old PayPal transaction id. This request is possibly fraudulent!
REQUEST INFO: ${params} """
}
else if (status == 'Completed') {
purchaseOrder.paypalTransactionId = params.txn_id
purchaseOrder.paymentStatus = PaymentStatus.COMPLETE
orderService.updateOrderStatus(purchaseOrder)
log.info "Verified payment ${purchaseOrder.paypalTransactionId} as COMPLETE"
} else if (status == 'Pending') {
purchaseOrder.paypalTransactionId = params.txn_id
purchaseOrder.paymentStatus = PaymentStatus.PENDING
orderService.updateOrderStatus(purchaseOrder)
log.info "Verified payment ${purchaseOrder.paypalTransactionId} as PENDING"
} else if (status == 'Failed') {
purchaseOrder.paypalTransactionId = params.txn_id
purchaseOrder.paymentStatus = PaymentStatus.FAILED
orderService.updateOrderStatus(purchaseOrder)
log.info "Verified payment ${purchaseOrder.paypalTransactionId} as FAILED"
}
}
}
}
else {
log.error "Error with PayPal IPN response: [$result] and Payment: [${purchaseOrder?.transactionId}]"
}
} catch (Exception e) {
log.error '"**************************************************************************'
log.error e
log.error '"**************************************************************************'
} finally {
render "OK" // Paypal needs a response, otherwise it will send the notification several times!
}
}
def success = {
log.info "Received IPN success from PayPal Server ${params}"
def uniqueKey = orderService.completeAndGetUniqueKeyByTransactionID(params.transactionId)
if (!uniqueKey){
response.sendError 403
return
}
log.info "Purchase Order complete for ${params.transactionId}"
flash.orderStatus = OrderStatus.COMPLETE
flash.transactionId = params.transactionId
flash.uniqueKey = uniqueKey
if (params.returnAction || params.returnController) {
def args = [:]
if (params.returnAction) args.action = params.returnAction
if (params.returnController) args.controller = params.returnController
args.params = params
redirect(args)
}
else {
chain(action:'termOfUse')
}
}
def cancel = {
params?.each { key, value ->
println "[${key}]\t=\t${value}\t::${value?.class?.name}"
}
log.info "Cancel Order for ${params.transactionId}"
def status = orderService.cancelOrder(params.transactionId)
if (!status){
response.sendError 403
}
flash.orderStatus = OrderStatus.CANCEL
flash.transactionId = params.transactionId
if (params.cancelAction || params.cancelController) {
def args = [:]
if (params.cancelAction) args.action = params.cancelAction
if (params.cancelController) args.controller = params.cancelController
args.params = params
redirect(args)
}
else {
chain(action:'termOfUse')
}
}
def termOfUse = {
if (flash.orderStatus == OrderStatus.COMPLETE || flash.orderStatus == OrderStatus.CANCEL){
return ['transactionId':flash.transactionId, 'uniqueKey':flash.uniqueKey, message:params.message]
}
def transactionId = params.transactionId ?: flash.transactionId
def uniqueKey = params.uniqueKey ?: flash.uniqueKey
def orderStatus = orderService.checkOrderStatus(transactionId)
if (OrderStatus.COMPLETE == orderStatus){
log.debug "Purchase Order complete for ${transactionId}"
flash.transactionId = transactionId
flash.orderStatus = OrderStatus.COMPLETE
flash.uniqueKey = uniqueKey
} else {
def order = orderService.createOrder(uniqueKey)
switch (order.paymentStatus) {
case PaymentStatus.FREE:
flash.transactionId = order.transactionId
flash.uniqueKey = uniqueKey
flash.orderStatus = OrderStatus.FREE
break
case PaymentStatus.INVALID:
flash.transactionId = order.transactionId
flash.uniqueKey = uniqueKey
flash.orderStatus = OrderStatus.INVALID
break
case PaymentStatus.PENDING:
flash.transactionId = order.transactionId
flash.uniqueKey = uniqueKey
flash.orderStatus = OrderStatus.CHARGE
break
default:
response.sendError 403
}
}
return ['transactionId':transactionId, 'uniqueKey':uniqueKey, message:params.message]
}
def buy = {
if (params.disagree){
return chain(action:'cancel', params:params)
}
def config = grailsApplication.config.com.risguru.plugin.ipn
def server = config.server
def receiver = params.email ?: config.receiver
if (!server || !receiver) throw new IllegalStateException("Paypal misconfigured! You need to specify the Paypal server URL and/or account email. Refer to documentation.")
def order = orderService.getOrderByTransactionID(params.transactionId)
if (!order){
response.sendError 403
}
def commonParams = [transactionId: order.transactionId]
def notifyURL = g.createLink(absolute: true, controller: 'payment', action: 'notify', params: commonParams).encodeAsURL()
def successURL = g.createLink(absolute: true, controller: 'payment', action: 'success', params: commonParams).encodeAsURL()
def cancelURL = g.createLink(absolute: true, controller: 'payment', action: 'cancel', params: commonParams).encodeAsURL()
def url = new StringBuffer("$server?")
url << "cmd=_xclick&"
// url << "business=${config.business}&"
url << "business=${receiver}&"
url << "item_name=${order.items[0].itemName}&"
url << "item_number=${order.items[0].itemNumber}&"
url << "quantity=${order.items[0].quantity}&"
url << "amount=${order.items[0].unitPrice}&"
url << "tax=${order.tax}&"
url << "currency_code=${order.currency}&"
if (config.test_ipn) {
url << "test_ipn=1&"
}
if (config.page_style){
url << "page_style=${config.page_style}&"
}
url << "notify_url=${notifyURL}&"
url << "return=${successURL}&"
url << "cancel_return=${cancelURL}"
log.debug "Redirection to PayPal with URL: $url"
redirect(url: url)
}
}
精彩评论